여기 번역한 문서를 별도의 도서로 만들거나 파일로 만들어 판매하지마세요.
저작권이 있는 문서 입니다. 상업적으로 이용하게 될 때는 직접적인 법적 조치가 들어갈지 모릅니다.

일단 저는 비상업적으로 번역하는 작업을 위해 얹어 봅니다.
만일 이렇게 비상업적으로 올리는 작업이 불법이라 하시면.. 알아서 자삭할께요.


머릿말

기업 환경 내에 Microsoft Windows XP 프로페셔날 버전을 배포하는 작업은 단순한 작업은 아닙니다. 이 작업에는 다양한 시나리오들을 예측해 상세하고 자세한 계획을 수립할 필요가 있습니다. 또한 적용이 가능한지 불가능한지를 판단할 수 있도록 각종 기술들에 대해 익숙할 필요가 있습니다. 이 책에서는 계획을 수립 중이나 계획에 따라 실행하는 과정 속에서 나올 수 있는 각종 의문 사항에 대한 답변 및 자세한 설명을 통해 여러분들을 도울 수 있을 것입니다.
또한 이 책에서 Windows XP 프로페셔날 버전 및 Microsoft Office 2003 Editions를 배포하기 위한 각종 기술 들 뿐만 아니라, 전체적인 배포 구조의 기틀을 잡아 드립니다.
예를 들자면  Windows XP 프로페셔날 배포 방법에 대한 단순한 설명 보다는 조직내 Windows XP 프로페셔날을 배포하기 위해 복사, 추가 변경사항 적용 및 사용할 수 있도록 도와줄 수 있는 배포 방법에 대한 틀을 제시해 드리게 됩니다. 또한 이런 틀을 통해 긴 파일 이름을 다루거나, 서드 파티 장치 드라이버 배포 및 Windows XP 프로페셔날 설치시에 포함될 응용 프로그램 설치 등을 자동적으로 수행할 수 있도록 도와드립니다.

윈도우 버전
이 책에서는 현재 Microsoft 제품 중 다음과 같은 버전들을 이용할 수 있도록 초점이 맞추어져 있습니다.

Windows XP 프로페셔날 이 책에서는 Windows XP 프로페셔날을 어떻게 배포하는지에 대해서 다룹니다. 이전 버전 윈도우에 대해서는 다루지 않습니다. 물론 Microsoft Windows 2000 프로페셔날과 유사할 수 있지만, Windows 2000에서는 제공되지 않는 Windows XP 프로페셔날에서만의 고유기능들이 있기 때문에 현재 이 책에서는 Windows XP 프로페셔날에 대해서만 다루고 있습니다.
Office 2003 Editions Windows XP 프로페셔날 배포 중에 Office 2003 Edition을 어떻게 포함 시킬 수 있는지에 대해 설명하고 있습니다. 물론 이전 Microsoft Office XP 배포 방법에 대해 어느정도 알고 있으면 많은 부분에 대해서 접근하는데 쉬울 수 있읍니다. 하지만, Office 2003 Edition에서만 제공되는 고유 기능들에 대해서는 다시 확인 하실 필요가 있습니다. 이 책에서는 그와 같은 고유 기능들에 대해서도 제공하고 있습니다.
Windows 2003 Server 아직 많은 곳에서 Windows 2000서버를 이용하고 있고, 계속 사용하려 하고 있기 때문에, Windows 2000 서버와 Windows 2003서버 중 어느쪽을 이용해야 할지 결정하기가 조금 어려웠습니다. 하지만, Windows 2003 서버가 가진 몇가지 기능들로 Windows 2003으로 결정했다. Windows 2000에 대해서는 기타 다른 서적을 참조하면 될 것 같습니다.

전문용어
이이 책에서 사용된 대부분의 전문 용어들은 현재 표준적으로 사용하는 내용이지만 혼란을 방지하기 위해 몇가지 단어에 대해서는 어떻게 이용했는지에 대해 설명드리도록 하겠습니다.또한 각종 경로들에 대한 표기들도 실제 경로를 적기 보다 표준 환경 변수내의 값들을 활용하여 표시하고 있습니다. 그래서, 해당 실행 문장을 그대로 적용할때, 적용할 환경의 기존 컴퓨터 내의 프로파일(C:\Documents and Settings or C:\Winnt\Profiles)값들이 무엇이든 간에 다양한 시나리오에 쉽게 적용하실 수 있습니다.
덧붙이면, Windows XP 프로페셔날 시스템에 담긴 폴더이 각기 다른 위치에 있을 수 있습니다. 대부분의 경우가 처음 사용자 설치가 아닌 운영체제 업그레이드 하는 경우나 자동 설치시 사용자 정의할 때 경로들을 추가적인 수정을 가한 경우에는 표준 위치 값들이 바뀌게 됩니다. 그래서 이 책에서는 환경 변수들을 통해 이용할 수 있도록 구성되었습니다.(MS-DOS 명령 프롬프트 상에서 환경 변수값들을 확인할 수 있습니다.

%USERPROFILE% 이 환경변수는 현재 사용자 프로파일을 나타냅니다.
예를들어, C:\Documents and Settings 폴더안에 있는 Jerry 라는 계정으로 컴퓨터에 로그인 했을 때, %USERPROFILE% 이 의미하는 바는 C:\Documents and Settings\Jerry이 됩니다.
%SYSTEMDRIVE% 이 환경변수는 현재 Windows XP 프로페셔날 시스템 파일이 저장된 드라이브를 나타냅니다. 일반적으로 C드라이브를 의미합니다. 하지만, 듀얼 부팅 설정을 했을 때와 같이 다른 드라이브에 설치하는 경우에는 D,E.. 등과 같은 다른 이름이 나오게 됩니다.
%SYSTEMROOT% 이 환경변수는 Windows XP 프로페셔날이 담긴 폴더를 나타냅니다. 처음 사용자 설치를 한 경우에는 일반적으로 C:\Windows 위치가 되지만, Windows NT 또는 Windows 2000 상에서 업그레이드를 수행한 경우에는 C:\Winnt의 위치가 됩니다.

환경 변수들 뿐만 아니라, 레지스트리의 다양한 루트 키들도 줄여서 표현 했습니다. HKEY_CLASSES_ROOT 및 HKEY_LOCAL_MACHINE 과 같은 경우 그대로 쓰기에는 해당 줄 자체가 넘어가는 경우가 많습니다. 그래서 보다 읽기 좋도록 다음과 같이 바꾸어 기록했습니다.

HKCR               HKEY_CLASSES_ROOT
HKCU               HKEY_CURRENT_USER
HKLM               HKEY_LOCAL_MACHINE
HKU                 HKEY_USERS
HKCC               HKEY_CURRENT_CONFIG

부록 DVD
이 부록 DVD에는 계획 수립에 도움을 줄 수 있는 양식 및 배포 위치에 설정할 예제, 도구, 및 책 전체 내용이 수록된 전자 책등이 담겨 있습니다. 또한 배포에 관련된 각종 백서들도 한곳에 모아 담아두고 있습니다. 부록 DVD내의 각 디렉토리에 담긴 내용들은 다음과 같습니다.:

Aids 이 폴더에는 업무에 도움이 될 내용들을 담고 있는데, 각종 체크 시트들과 같은 계획 수립을 위한 양식들을 담고 있습니다. 업무를 수행할 때 필요한 사항만 수정하여 바로 적용할 수 있도록 되어 있으므로 간단하게 이용할 수 있읍니다.
Extras 이 폴더에는 유용한 각종 문서 및 백서들을 담고 있습니다. 대부분 Microsoft에서 제작한 내용입니다. Extra의 하위 폴더로 각 장 별로 나누어 구성되어 있어 해당 장별로 관련된 문서를 쉽게 찾을 수 있습니다.
Favorites 이 폴더에는 이 책에서 언급된 하이퍼링크들에 대한 바로가기들을 담고 있습니다. 여기있는 내용을 "즐겨 찾기" 상에 직접 옮겨 놓으면 보다 빠르게 이용할 수 있습니다.
Samples 이 폴더에는 예제 파일 및 배포 위치들을 담고 있습니다. 이 폴더에는 각 장에서 제공하는 예제들을 담고 있어 해당 장에 대한 예제 내용을 바로 실행해 보실 수 있습니다. 하지만 대부분의 경우에는 자신의 컴퓨터에 예제를 복사 한뒤 지침서 안에 담긴 내용들을 이용해 원하는 방법대로 수정하여 구성하시면서 실행해 보시면 됩니다.
Scripts 이 폴더에는 배포 시나리오에서 유용한 다양한 스크립트 들이 담겨 있습니다. 몇가지는 Windows Script Host 스크립트도 그 외 것들은 배치 파일들입니다. 대부분의 경우에는 특별한 명령 줄 옵션 없이 스크립트 입력만 입력해 실행하면, 각 명령줄에 대한 설명을 제공합니다.

이 책에 바로 나열된 스크립트들은 실행보고 난 뒤 CD 안에 담았기 때문에, 실제 책 내의 스크립트 소스 내용과 CD의 내용의 소스가 다르다면, CD의 내용의 소스 내용이 최신 버전이 됩니다. 테스트 할 때 CD 버전의 소스를 기준으로 테스트해보시면 됩니다.

시스템 요구사항
Windows XP 프로페셔날이 배포될 컴퓨터는 Windows XP 최소사양에 부합해야 합니다.(이 부분에 대한 자세한 사항은 이 책의 계획 장에서 확인하시기 바랍니다.). 스크립트의 대부분은 최소한 Windows 2000 Professional, Windows 2000 Server, Windows XP 또는 Windows Server 2003의 컴퓨터에서 정상적으로 동작합니다. 전자 책의 내용을 보시려면 어도브 아크로뱃 및 어도브 아크로뱃 리더를 활용하시면 보실 수 있습니다. 각 응용 프로그램에 대한 자세한 정보를 보시거나 어도브 아크로뱃 리더를 다운로드 받으시려면  www.adobe.com에 방문하시기 바랍니다.

연계 웹사이트
이 책을 통해 얻고자 하는 것은 배포 작업을 보다 쉽게 해드리는 것입니다. 이 책이 거의 800여페이지 분량이지만, 모든 형태의 시나리오들에 대해 다룰 만큼 충분한 내용을 모두 다루고 있다고는 생각하지 않습니다. 물론 몇가지 사항들은 적은 양의 페이지만으로 대부분의 내용을 담겨 있습니다. 예를 들자면, 그룹 정책과 같은 경우, 이 책에서는 단지 60여 페이지안에 다루고 있지만, 그 기술에 대한 전체 내용은 거의 300여 페이지에 달합니다. .
그럼 왜 이 책에서 실시간 책을 지향하는 이유는 무엇일까요? 그 이유는 연계 웹사이트를 통해 지속적으로 책의 내용을 보충하고 지원하고 있기 때문입니다. 각종 예제, 새로운 항목, 새로운 내용들을 연계 웹사이트를 통해 제공 하고 있습니다.또한 책 안에서 잘못 기록한 사항들 역시 웹사이트를 통해 계속 수정합니다. 무엇보다 중요한 것은 이 웹사이트에는 배포 커뮤니케이션 이라는 여러분들과 함께 의논하고 작업할 수 있는 게시판을 제공하고 있다는 사실입니다. 저는 이 게시판 시스템을 통해 각종 질문들에 대해 답변하고, 다른 독자들에게도 유용한 답변들을 쉽게 볼 수 있도록 하는 것입니다. 연계 웹사이트의 경로는 http://www.bddreskit.com 입니다.

기타 자료
Windows XP 프로페셔날과 오피스 2003 에디션을 배포할 때 반드시 필요한 세가지 참고 자료들이 있습니다. 이 책의 대부분의 항목에서 활용될 사항들입니다. 이들 자료들은 다음과 같습니다.

Windows XP 프로페셔날 배포 도구 이 도구들은 Windows XP 프로페셔날 시디안에 Support\Tools 폴더 안에 있는 내용입니다. 그 중 Deploy.cab 이라는 파일안에 담겨 있는데, 그 안의 내용은 윈도우 익스플로우를 이용하여 들어가보시면 안의 내용을 확인하실 수 있습니다. 안의 내용 중에 “Microsoft Windows Corporate Deployment Tools User’s
Guide,”라는 문서를 보실 수 있는데, 자동 설치 응답 파일과 같은 각종 배포 파일에 대한 각종 설명 자료료가 담겨 있습니다. Deploy.chm과 Ref.chm이 바로 그 내용의 문서입니다.가급적 이 두개의 문서 파일은 내 문서 폴더등에 복사하여 언제든지 확인해서 볼 수 있도록 하는 것을 권장합니다.
Office 2003 Editions Resource Kit 이 리소스 킷은 Office 2003 에디션의 설정을 변경하거나 배포 할 때 필요한 각종 문서와 도구들을 제공하고 있습니다. 이 리소스 킷은  http://www.microsoft.com/office/ork 에서 확인하실 수 있습니다.
Microsoft Solution Accelerator for Business Desktop Deployment The
Microsoft Solution Accelerator for Business Desktop Deployment (BDD) 에서는 Windows XP 프로페셔날과 Office 2003 에디션 및 기타 중요 업무용 응용 프로그램들을 조직 전체에 보다 빠르게 배포할 수 있도록 각종 지침서 및 도구들을 제공합니다. 각각의 기술 문서에는 신속한 배포를 하기 위한 계획과 수행 방법들을 담고 있습니다. 또한 현업 환경에서 데스크톱 배포 프로젝트를 수행, 관리, 운영전환에서 이용될 각종 예제 문서 및 양식들도 담고 있습니다. 이 BDD solution accelerator는 http://www.microsoft.com/downloads
에서 다운로드 받으실 수 있습니다.물론 여기서 제공되는 솔루션이 정확하게 맞아 들어가진 않겠지만, 배포 프로젝트를 수행하는데 훌륭한 시작점이 될 수 있습니다. 또한 이 책의 내용과 잘 부합되는 사항들이기도 합니다.

Lab 테스트 하기
여기서 언급되는 사항들은 단순 따라하기 기술 서적은 아닙니다. 예를 들면, 윈도우 서버 2003에 대한 소개같은 글은 아니라는 것이죠. 보통 그런 책들은 전반적인 지식이 없어도 대략적인 사항들을 파악할 수 있도록 다소 구체적인 방법으로 서술 되어 있습니다. 구성되어 있습니다. 하지만, 이 책에서는 구체적으로 특정 조직이나 인프라스트럭처 그 외의 업부 보안등으로 구성된 내용이 아니라 전반적이며 일반적인 형태로 구성되어 있습니다. 대 규모 데스크톱 배포를 성공적으로 수행하기 위해서는 조심스러운 계획과 테스트가 반드시 필요합니다. 그래서 이 책에서는 보다 기술적인 중심 생각들을 담았고, 조직 내 배포 구성을 하기 위한 계획과 각종 테스트하는 방법들을 보여드리고 있습니다.

Resource Kit 지원 정책
Microsoft 에서는 Microsoft Windows Desktop Deployment Resource Kit 포함 시디 상의 각종 도구 및 스크립트에 대한 지원을 하지 않습니다. 또한 각 도구 및 스크립트 예제 대한 성능 및 숨겨져 있는 버그들에 대해 보장해 드리지 않습니다. 하지만, Microsoft Press에서는 별도로 이메일인 msinput@microsoft.com를 통해 책 내용 및 각종 소프트웨어에 대한 문제를 알려주시면  이 책을 구입한 고객 분들에게 각각의 문제들을 다양한 방법을 통해 알려드릴 것입니다. 단 이 이메일 주소는 오로지 Microsoft Windows Desktop Deployment Resource Kit 과 관련된 문제 들만 받아 들입니다. 그 외에 Microsoft Press에서는 http://www.microsoft.com/learning/support/ 웹사이트를 통해서도 지원합니다. 그 외에 문제 및 질문에 대한 사항을 입력하거나 Microsoft Press Knowledge Base 상에 직접 연결하려면  http://www.microsoft.com/learning/support/search.asp. 로 접속하기시기 바랍니다. 또한 Windows 운영체제에 관련된 문제사항이 발생된 경우 각 제품의 지원 정보등을 확인하여 참고하시기 바랍니다.

저자에게 연락하는 방법
만일 저에게 알릴 사항이나 질문이 있다면,  jerry@honeycutt.com. 으로 내용을 보내 주시기 바랍니다. 최대한 빨리 E-Mail을 통해 답변을 보내드리겠습니다. 또한 웹사이트 http://www.honeycutt.com 를 방문하시면 배포 서비스에 관련된 각종 내용들을 최대한 지원할 수 있는데까지 지원해드리겠습니다.

728x90
쉬엄 쉬엄 취미생활로 작업하고 있습니다.
그래서 인지 영어가 딸리는 부분이 많아 오역해 버리는 경우도 많습니다.
또한 오탈자도 만만치 않고...
게다가 단어 사전도 없어서, 이렇게 표현 했다가 저렇게 표현 했다가
합니다 ^^;;;

나중에 PHP를 공부해서 단어 사전을 만들던지 하죠.

뭐, 일단 오역, 오탈자가 발견되면 말씀해 주세요.
잡게시판도 좋고, 덧글도 좋고..(덧글은 제가 잘 못보니깐....문제가 있지만...)

그리고 같이 번역을 하고 싶으신 분은 원본 문서가 있는 링크를 알려주세요.
겹치지 않으면 계정에 쓰기 권한을 드릴께요.

그럼~
728x90
원본 : http://www.codeproject.com/wtl/wtl4mfc1.asp

Readme.TXT

이 문서의 의견 게시판으로 메시지를 보내기전에 먼저 이 문서를 읽어주시기 바랍니다.

제일 먼저 Platform SDK가 필요합니다. 이 SDK가 없으면 WTL을 사용할 수 없습니다. Platform SDK는 온라인 SDK 업데이트 사이트를 이용하거나, 따로 CAB 파일등을 다운 로드받아 직접 설치를 하셔도 됩니다. 꼭 확인 해야 되는 것은 VC 옵션안에 있는 include 와 lib의 VC 검색 디렉토리를 등록해주는 유틸리티를 꼭 실행해주셔야 됩니다. 자동 등록해주는 프로그램은 Platform SDK 프로그램 그룹안에 Visual Studio Registration이 바로 그 프로그램 입니다.

WTL 필요하다면 마이크로소프트에서 버젼 7을 다운로드 받으시기 바랍니다. 더 자세한 설명이 필요하시면 "Introduction to WTL - Part 1"과 "Easy installation of WTL" 문서를 보시면 됩니다.  그 문서들은 세월이 흐른 오랜된 문서일지는 모르겠지만, 나름대로 훌륭한 정보들을 담고 있습니다. 그 중 몇가지 중 하나가 WTL include 디렉토리를 VC상에서 어떻게 연결 시키는지에 대해 언급해 놓고 있습니다. VC 6에서 한다고 하면 Tools|Options 메뉴를 선택 한 뒤 Directories 탭으로 가서 콤보 박스를 이용해 Show directories를 고른 후, Include files를 선택해 주시기 바랍니다.  그리고 난뒤 새로운 항목을 추가해서 WTL 헤더 파일들이 어디에 있는지를 지정해 주시면 됩니다.

먼저 MFC에 대해 어느정도 습득하고 있어야 합니다. 먼저 메시지 맵 매크로에 대해 충분히 이해 할 수 있어야 됩니다. 즉, DO NOT EDIT 라고 적혀진 코드를 망설임 없이 수정할 수 있으며 별 문제가 없어야 된다는 거죠.

그리고 Win32 API 프로그래밍에 대해서도 잘 알고 익숙하게 다룰 수 있어야 합니다. 혹시 윈도우 프로그래밍 공부를 MFC부터 시작해서 API 단계에서 메시지가 어떻게 작동하는지 잘 모른다면, WTL에서 많이 헤맬 수 있습니다. 게다가, 메시지의 WPARAMLPARAM에 대한 의미를 전혀 모른다면 차라리 다른 문서들(CodeProject 안에도 상당히 많습니다.)을 읽고 이해하고 들어가는 것이 좋을듯 싶습니다.

마지막으로 C++ template 문법을 알고 있어야 합니다. 이 부분은 C++ FAQs와 template FAQs에 연결된 VC Forum FAQ를 이용하시면 많은 참고가 될 겁니다.

이 후에 제가 모든 설명은 VC 6기준으로 작성되겠지만, 최소한 제가 아는 범위에서는 VC 7에서도 잘 동작하는 것으로 알고 있습니다. 물론 VC 7을 사용해 본적이 없어 VC 7상에서 발생된 문제를 직접적으로 도와드리긴 어려울지 모르겠습니다. 하지만 VC 7자체의 문제가 아니고 WTL에 관한 문제라면 얼마든지 도와 드릴 수 있습니다.

연재하기에 앞서..

WTL을 굳이 한마디로 표현하면 단단한 돌이라고 할까요? WTL은 MFC같은 수많은 GUI 클래스들을 가지고 있지만, 실제 결과물은 요점 정리되듯 최소화 되서 만들어 집니다. 물론 제가 만든 예제들을 여러분께서는 지금까지 익혀오신 MFC를 사용하여 작성한다면 MFC에서 제공되는 랩퍼 클래스로 손쉽게 작성할 수 있게 되고 메시지 처리도 편하고 간단하고 얼마든지 수정 가능하도록 작성하실 수 있읍니다. 그러나 몇가지 구현하지도 않았는데, 알게 모르게 수백K의 용량이 저절로 붙어 버립니다. 이런 것이 싫다면 바로 WTL을 선택하시면 정답이 될 수 있읍니다. 물론 그 전에 WTL이 가진 약점 몇가지는 꼭 염두해 두시기 바랍니다. WTL이 가진 약점들은 다음과 같습니다.

- ATL 스타일 템플릿은 맨 처음 접해보신 분이라면 겁부터 먹게 만들어 줍니다.
- ClassWizard의 기능은 전혀 쓸 수가 없습니다. 결국 메시지맵을 작성하려면 메뉴얼 부터 붙잡고 있어야 됩니다.
- MSDN에 문서화 되지도 않았습니다. 도움말은 다른 곳에서 찾아야 되고 그것마저 여의치 않다면 WTL로 작성된 소스를 보셔야 될겁니다.
- 아마 서고에 꽂아놓고 볼만 한 참고 도서를 찾기가 수월치 않습니다.
- "Microsoft에서 공식적으로 지원하지 않습니다" 라는 문구가 졸졸 따라 다닐 겁니다.
- ATL/WTL 윈도우 작업은 확실히 MFC 윈도우 작업과는 전혀 틀리기 때문에, MFC로 작업하신 분은 자신이 알고 있는 지식을 훌쩍 뛰어 넘는 것을 확인하시게 됩니다.

그럼 이제는 WTL의 장점을 살펴 보도록 하죠.

- 더 이상 복잡스러운 Doc/View 구조에 대해 고민 하실 필요가 전혀 없습니다.
- MFC에 구현된 기초적인 UI적 특징을 가지고 있으며, DDX/DDV 와 "update command UI" 기능을 포함하고 있습니다.
- 실제적으로 MFC의 기능보다 향상된 것도 가지고 있습니다. (예를 들면 좀 더 유연한 구조의 splitter windows)
- Static-Lincked MFC 프로그램 보다 훨씬 작아진 실행파일을 생성합니다.
- WTL 자체 버그 수정도 가능하기 때문에, 일일히 패치를 기다리며 고민할 필요가 없습니다.(버그 내용이 MFC/CRT DLL안에 있게 되면, 결국 이 문제는 Microsoft가 패치를 내놓지 않는한 다른 프로그램에서도 계속 발생할 수 있게 됩니다.)
어떻게든 MFC가 필요하다면, MFC 와 ATL/WTL 윈도우 작업은 아무런 문제없이 겸해서 작동 시킬 수 있습니다.( 저 같은 경우에는 프로토타입 제작하는 동안에 MFC의 CDialog를 띄워주는 WTL SplitterWindow를 가진 MFC CFrameWnd를 생성해 본적 이 있습니다. -- 물론 시연이 끝났을때는 전부 WTL 코드로 수정해 버렸죠)  

현재 이 시리즈는 ATL 윈도우 클래스에 의해 만들어지게 되었습니다. 여기에 ATL 클래스에서 추가하고 추가해서 현재까지 버젼이 탄생했는데, ATL 윈도우 작업에 대해서 잘 알고 있다면 훨씬 수월한 작업이 될 수 있습니다. 저도 이렇게 전체적으로 ATL에 대해 공부하고 난뒤에는 WTL에 대한 접근이나 GUI 프로그래밍 할 때 훨씬 수월하게 작업할 수 있게 되었습니다.

Part I에 대해

WTL은 상당히 매력적입니다. 하지만 무엇보다 선행되야 하는 건 ATL에 대한 전반적인 이해 입니다. WTL은 ATL 클래스를 모아서 추가시킨 셋이기 때문에, MFC로만 작업하신 분이라면 ATL의 GUI 클래스들의 압박을 이겨 낼 수 있어야 됩니다. 그렇기 때문에 막바로 WTL의 사용하기 전에 저와 함께 끈기를 가지고 ATL 속으로 빠져드는 작업을 해주셔야 합니다.

첫번째 Part에서는 ATL의 약간의 배경 지식을 먼저 소개 하고, ATL 코드를 작성하기 전에 기초적인 몇가지 사항을 짚어 보고, 웃기는 ATL 템플릿에 대해 빠르게 설명하고, 기초적인 ATL 윈도우 클래스들을 살펴 볼 것입니다.

ATL의 배경

ATL과 WTL 역사
The Active Template Library... 묘한 이름이지 않나요? 예전 분들 같은 경우에는 ActiveX Template Library 라고 기억하신 분들도 있을지 모르겠군요. 사실 그 표현이 더 정확하게 표기 한 것입니다. 왜냐면 ATL의 목적은 COM 개체와 ActiveX 컨트롤을 보다 쉽게 작성할 수 있도록 만들어 놓은 것이기 때문입니다. (ATL은 당시에 Microsoft 에서 새로운 ActiveX 제품을 만들 때 붙여진 이름이기 때문에 그렇게 쓰였다가 요즘 .NET 제품들을 뽑아내려다 보니 바뀌었다고 생각하셔도 될겁니다.) 보통 ATL은 MFC에 있는 CWnd와 CDialog 형태의 기초적인 GUI를 구현한 클래스들을 가지고 있어서 COM 개체를 작성할 때 많이 쓰였습니다. 다행히, GUI 클래스들은 WTL 같은 것을을 만들 수 있을 만큼 유연한 구조를 갖추고 있었죠.

WTL은 현재 두번째 큰 개조가 있었습니다. 첫번재는 3.1 버젼에서 였고, 두번재는 7 버젼에서 였습니다 .( 버젼넘버는 ATL 버젼에 따라 선택된 것이기 때문에, 일반적으로 하는 1, 2,.. 이런식으로 나가지 않습니다.) 3.1 버전에서는 VC6과 VC7에서 작동되는데, 단 VC7에서는 VC7에 맞추어진 별도의 선행처리기용 선언문들을 통해 동작했습니다. WTL 7 버젼은 3.1 버젼에서 대체되어 구성되었습니다. 그래서 VC 7에서 별도의 선행처리 작업이 필요 없게 되었습니다. 그래서, WTL을 이용하여 개발할 때 더 이상 3.1을 사용할 필요가 없어졌습니다.
ATL-style template

C++ 템플릿에 대해 어느정도의 경지에 오르신 분들도 주의할 사항이 있습니다. ATL 속에 숨겨진 함정이 두가지가 있기 때문입니다.
먼저 다음 클래스를 보시기 바랍니다.
class CMyWnd : public CWindowImpl< CMyWnd >
{
...
};

실제적으로는 맞는 코드 입니다. C++의 규칙대로 해석하자면 CMyWnd 클래스(정확히는 class CMyWnd 뒷 부분을 보시면 됩니다)는 상위의 템플릿에게서 상속받고 선언되었음을 의미합니다. 템플릿 파라미터에 들어갈 클래스 이름을 넣었기 때문인데, 이부분에서 두번째의 ATL의 함정이 있기 때문입니다. 결론을 부터 말하자면 컴파일 될때, 버추얼 함수를 호출을 합니다.

template < class T >
class B1
{
public:
    void SayHi()
    {
    T* pT = static_cast< T* >(this);   // 허걱? 이 부분을 아래에 설명하겠습니다.

        pT->PrintClassName();
    }
protected:
    void PrintClassName() { cout << "This is B1"; }
};

class D1 : public B1< D1 >
{
    // 전체적으로 오버라이딩 된 함수가 없습니다.
};

class D2 : public B1< D2 >
{
protected:
    void PrintClassName() { cout << "This is D2"; }
};

main()
{
D1 d1;
D2 d2;

    d1.SayHi();    // prints "This is B1"
    d2.SayHi();    // prints "This is D2"
}

static_cast(this)에 그 숨겨진 속셈이 있습니다. 타입 B1* 인 this를 캐스팅 하면서 D1*이나 D2* 형태로 this 즉 B1* 형을 캐스트 하게 되면 호출 시에 특수하게 호출됩니다.
왜냐면 템플릿 코드는 컴파일시에 코드가 생성되기 때문에, 이런 캐스팅에 안정성을 보장 받을 수 있게 되어 올바른 상속받은 리스트로 쓰여지게 됩니다.
하지만, 다음과 같은 쓰게 된다면 문제가 발생 할 수 있습니다.
 class D3 : public B1< D2 > 

this은 D1*과 D2* 형에 대해서만 안정성을 보장 받을 수 있고 그 외에 것에서는 해당되지 않습니다. 주의할점은 이 방법에서 적용된 방법은 일반적인 C++ 다형성과 거의 동일한 형태이기 때문에, SayHi() 메소드는 가상함수가 되지 않습니다.

이게 어떻게 작동하는지 자세하게 설명하기 위해서는 먼저 SayHi()를 호출하는 것 부터 보아야 합니다. 제일 처음 호출 되면, 특수화 된 B1이 사용됩니다. 그리고 SayHi() 코드는 다음과 같이 표현됩니다.
 void B1< D1 >::SayHi()
{
D1* pT = static_cast< D1* >(this);

    pT->PrintClassName();
}

D1이 PrintClassName()을 override를 하지 않았기 때문에, D1의 base class에서 그 메소드를 찾게 됩니다. B1은 PrintClassName() 메소드를 가지고 있기 때문에, 일단 그것을 호출하게 됩니다.
자 이젠, 두번째 SayHi()를 살펴보도록 하죠. 이 때는 특수화 된 B1를 사용하게 되고 이를 완전한 코드 형태로 만들게 되면 다음과 같이 표현됩니다.
 void B1< D2 >::SayHi()
{
D2* pT = static_cast< D2* >(this);

    pT->PrintClassName();
}

이때, D2는 PrintClassName() 메소드를 가지고 있기 때문에, 바로 그 함수가 실행되게 됩니다.

이 기술을 사용하게 되면 얻는 이점을 살펴보도록 하죠
- 객체의 포인터 사용이 별 필요 없게 됩니다.
- 가상 함수 테이블이 필요없으므로 메모리가 절약 됩니다
- 초기화 되지 않은 가상 함수 테이블로 인해, 실행시에 널 포인터로 된 가상 함수를 호출하는 것이 가능합니다.
- 모든 함수는 컴파일시에 표현 되기 때문에, 자체적으로 최적화를 갖출 수 있게 됩니다.

가상 함수 테이블로 얻게 되는 장점은 이 예제에서는 그다지 특이해 보이지 않을지 모릅니다.(측정해 보면 고작 4 byte 정도 밖에 안됩니다.)
하지만 15개의 base class에, 각기 20개 이상의 메소드를 가지고 있다고 생각해 보세요. 상당한 메모리 절약을 얻을 수 있습니다.


ATL 윈도우 클래스들


네 이정도면 충분히 배경 설명이 된것 같습니다. 이제 ATL 세계에 본격적으로 빠져 보도록 하죠. ATL은 정확히 인터페이스/구현 부를 나누어 설계되어 있기 때문에, 윈도우 클래스에서 역시 정확히 나누어져 있습니다. 이 점은 COM과 유사하게 되어 있습니다.COM 역시 인터페이스 선언은 구현 부분과는 완벽하게 나뉘어져 있죠.(물론 몇가지 구현은 제외되지만..)

ATL은 윈도우를 위해 정의된 "인터페이스" 클래스를 가지고 있습니다. 그것을 통해 윈도우 작업을 수행할 수 있죠. 그 클래스는 CWindow라고 불립니다.특이한 클래스라기 보다는 단지 HWND를 랩핑한 수준의 클래스입니다. HWND는 일반적인 API 사용자라면 익숙하게 사용하고, 제일 처음 접하게 되는 파라미터죠. SetWindowText()나 DestoryWindow() 함수만 봐도 알 수 있죠. CWindow는 m_hWnd를 public member로 가지고 있기에 HWND가 필요하다면 손쉽게 접근이 가능합니다. 또 CWindow는 operator HWND 메소드를 가지고 있기 때문에, HWND를 손쉽게 CWindow로 전달 할 수 있습니다. 하지만 CWnd::GetSafeHwnd()와는 전혀 다른 값입니다.

CWindow는 MFC의 CWnd와는 거의 완벽할 정도로 틀립니다. CWindow 객체는 단 한개의 데이터 member를 가지고 있기 때문에, 생성하는데 별 비용이 들지도 않고, MFC에서 내부적으로 HWND와 CWnd를 매핑 하는 객체 맵과 전혀 틀립니다. 당연히 CWnd와는 전혀 다른 범주에 있기 때문에, 관련된 윈도우를 파괴하는 일 따윈 없습니다. 다시 말하자면 임의로 생성한 CWindow 객체를 일일히 detach 할 필요가 없다는 의미입니다.

ATL의 Window 구현 부분은 CWindowImpl에서 담당하게 됩니다. CWindowImpl에는 Window Class 등록과 같은 작업이나 윈도우 서브클래싱, 메시지 맵, 기본적인 WindowPoc()과 같은 기본적인 코드를 담고 있습니다. 당연히 CWnd와 같이 하나의 클래스에서 모두 끝내는 MFC와는 전혀 틀립니다.

또 대화 상자를 구현하는 부분도 CDialogImpl 과 CAxDialogImpl, 두가지 가 있습니다. CDialogImpl은 일반적으로 사용되는 보통 대화상자를 구현할 때, CAxDialogImpl은 ActiveX 컨트롤 사용하는 대화상자를 구현할때 사용됩니다.

윈도우 생성을 위한 설정


대화상자 형태의 윈도우가 아니라면, CWindowImpl을 상속 받아 생성할 수 있습니다. 새로운 클래스를 생성하기 위해서는 다음 세가지 속성을 가지고 잇어야 합니다.
1. Winow class 기본 설정 내역
2. 메시지 맵
3. 윈도우 속성이라고 일컫는 윈도우 자체가 가진 기본 스타일

Window class 기본 설정 내역이란, DECLARE_WND_CLASS 또는 DECLARE_WND_CLASS_EX 매크로를 사용하는 것을 의미합니다. 이 두가지 define 내용을 다르게 표현한다면, WNDCLASSEX 구조체를 감싸만든 ATL용 구조체인 CWndClassInfo를 구성하는 것입니다.
DECLARE_WND_CLASS는 새 윈도우 클래스 이름을 자동 생성과 동시에 다른 멤버들의 기본값들을 자동적으로 제공하며, DECLARE_WND_CLASS_EX를 사용하게 되면 해당 클래스 스타일과 배경색 까지 설정할 수 있게 됩니다.    
물론 클래스 이름을 NULL로 설정할 수 있는데, 그 때는 ATL에서 자동적으로 이름을 생성해 줍니다.

일단 Window Class 기본 설정 내역은 파면 팔수록 복잡하지만 아래의 소스처럼 쓰면 사실 끝입니다. 일단 여기까지만 하고, 다음 이야기를 계속 하겠습니다.
class CMyWindow : public CWindowImpl
{
public:
    DECLARE_WND_CLASS(_T("My Window Class"))
};


그 다음으로 알아볼 것은 바로 메시지 맵입니다.
ATL 메시지 맵은 MFC의 메시지 맵보다 더 단순합니다. ATL의 맵은 거대한 switch 문형에 조금 변형을 가한 정도랄까요?; Switch문형 처럼 우항에 위치한 대응되는 함수를 호출합니다. 메시지 맵의 매크로는 BEGIN_MSG_MAP 과 END_MSG_MAP 이 두개 입니다. 한번 윈도우안에 빈 메시지 맵을 추가해보도록 하죠.
class CMyWindow : public CWindowImpl
{
public:
    DECLARE_WND_CLASS(_T("My Window Class"))

    BEGIN_MSG_MAP(CMyWindow)
    END_MSG_MAP()
};

메시지 맵안에 핸들러를 삽입하는 방법은 다음 섹션에서 다루기로 하겠습니다.
마지막으로 윈도우 속성을 적용하는 법을 다루어 보도록 하겠습니다.  
728x90
몇가지씩 보이는 암호 스파이질 하는 방법에 대해서 The Code Project에 소개 되어 있습니다. 하지만, 대부분은 Wndows 후크 정도의 처리로 끝납니다. 그러한 유틸리티를 제작할때 다른 방법으로 한느 방법은 없을까요? 물론! 있습니다. 하지만, 먼저 몇가지 문제점들을 살펴보고 그 문제를 해결하면서 만들 수 있는 방법을 모색하는 것이 좋겠습니다.

다른 컨트롤의 내용을 읽어온다는 방법 - 그것이 여러분 프로그램안에 속해 있는 내용이든 아니던 간에 - 을 제시하라고 하면 보통 WM_GETTEXT 메시지를 보내 처리하곤 합니다. 이 방법은 에디트 컨트롤에서 제공되고 있읍니다. 단, 특별한 경우만 제외하면 말입니다. 만약 에디트 컨트롤이 다른 프로세스에서 동작하고 있고, ES_PASSWORD 스타일이 적용된 경우라면 실패하게 됩니다. 단지 자신의 프로세스안에 있는 암호처리된 컨트롤 내용을 WM_GETTEXT 할 수 있다는 것이죠. 그러면 우리들이 가진 문제는 다음과 같이 요약될 수 있습니다. 어떻게 가져올것인가...

::SendMessage( hPwdEdit, WM_GETTEXT, nMaxChars, psBuffer );
다른 프로세스 상의 주소 공간안에 실행된 상태

일반적으로 문제를 푸는 방법이 3가지가 있습니다.

1. DLL안에 코드를 넣고, Windows 훅크를 통해 remote process에다 DLL를 맵핑하는 것입니다.  
2. DLL안에 코드를 넣고, DLL을 remote process에 맵핑하는 방법으로는 CreateRemoteThread 와 LoadLibrary 기술을 이용해서 해결할 수 있습니다.
3. 분리된 DLL을 쓰는 대신에 WriteProcessMemory를 통해 remote process에 직접 코드를 복사해 넣고 하는 방법으로 CreateRemoteThread로 실행하는 방법입니다. 이 기술의 자세한 설명은 여기서 찾을 수 있습니다.

I. Windows Hooks

Demo applications: HookSpy and HookInjEx

Windows 훅크의 원래 목적은 몇가지 스레드들의 메시지 소통을 감시하기 위해서 입니다 . 보통 다음과 같이 분류될 수 있습니다.

  1. Local hooks, 여러분의 프로세스에서 구성된 스레드들의 메시지 소통이 어디서 벌어지는 확인 할때
  2. Remote hooks, 다음 두가지로 분류될 수 있습니다. :
      a. thread-specific, 다른 프로세스에 속해진 쓰레드의 메시지 소통을 감시할때 ;
      b.system-wide, 현재 시스템에서 동작되는 모든 스레드들의 메시지 소통을 감시할때 .

만약, 다른 프로세스에 속해진 스레드들(간단히 2a, 2b 라고 하죠)를 후킹한다고 할때, 훅크 수행은  dynamic-link library (DLL)안애서 수행됩니다. 즉, 시스템이 훅크될 스레드의 주소 공간안에 훅크 수행할 내용을 담은 그 DLL을 맵핑하는 것이죠. Windows는 전체 DLL을 맵핑할 뿐이지, 프로시저를 후킹한다고 볼 수 는 없습니다. 이 방법으로 Windows 후크가 다른 프로세스의 주소공간안에 코드를 추가하여 사용할 수 있게 합니다.

하지만 여기서는 이 방법에 대해서 더 이상 언급하지 않겠습니다. (MSDN안의 SetWindowHookEx API부분을 살펴보시면 더 자세하게 나와 있습니다. ), 이 문서에서는 그 외의 두가지 방법을 다루려고 합니다. 그전에 한가지 주의사항을 언급하고 넘어가도록 하겠습니다.

1. SetWindowsHookEx를 호출 하는 것을 성공한 후, 시스템은 자동적으로 후킹된 스레드의 주소공간안에 DLL를 맵핑하게 됩니다. 그러나, 반드시 곧바로 적용되는 것은 아닙니다. 그 이유가 Windows 후킹이라는 것은 메시지로서 움직이기 때문이죠. 그런 문제로 DLL은 확실하게 이벤트가 발생될 때까지 해당 DLL을 설치하지 않게 되죠. 한가지 예를 들어보겠습니다.  
만약 어떤 스레드의 큐적용이 안된 모든 메시지를 후킹하도록 설치했다면(WH_CALLWNDPROC), 적용될 DLL은 후킹될 스레드에 실제로 메시지가 전달 될때 까지 리모트 프로세스에 맵핑되지 않게 됩니다. 다시 말하자면, 만약 후킹될 스레드가 메시지를 받기 전에 UnhookWindowsHookEx를 호출하게 되면, 그 DLL은 리모트 프로세스안으로 절대 맵핑되지 않게 됩니다. (비록 SetWindowsHookEx 호출 자체가 성공해도 말이죠).강제로 즉시 매핑을 시도하려면, SetWindowsHookEx를 호출한 후 바로 적용될 수 있게 만드는 이벤트를 해당하는 스레드에게 보내주어야 합니다.  

UnhookWindowsHookEx. 를 부른 후에도 위와 같은 이유로 그렇게 처리해 주어야 합니다. 그 DLL은 이벤트가 발생되기 전까지는 절대 매핑이 풀리지 않기 때문입니다.


2. 후크를 설치할 때, 후킹 작업은 전체적 시스템 퍼포먼스에 영향을 미칩니다. (system-wide 후킹은 예외입니다). 하지만, 메시지를 거르는 형태가 아니라, DLL 매핑 메커니즘같이 단독적으로 스레드에 한해서 후킹을 사용한다면 이 문제점을 간단하게 해결할 수 있는 방법이 있습니다. 다음에 제시되는 짧은 코드 내용이 바로 그것입니다.

BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved )
{
    if( ul_reason_for_call == DLL_PROCESS_ATTACH )
    {
        // Increase reference count via LoadLibrary
        char lib_name[MAX_PATH];
        ::GetModuleFileName( hModule, lib_name, MAX_PATH );
        ::LoadLibrary( lib_name );

        // Safely remove hook
        ::UnhookWindowsHookEx( g_hHook );
    }    
    return TRUE;
}

자, 저렇게 하면 무슨일이 벌어질까요? 먼저, Windows 후킹을 통해 리모트 프로세스로 DLL을 매핑합니다. 그리고 난뒤, DLL을 실제적으로 매핑하고 난 뒤 후킹될 스레드가 메시지를 받기도 전에 즉시 그 DLL을 매핑에서 해제합니다. 여기서 재미있는 점이, LoadLibrary를 통해 DLL의 참조 카운트가 증가되서 매핑이 풀리는 것을 막게된다는 것입니다.

자, 여기서 남아 있는 숙제, 프로그램이 종료 될때, 어떻게 그 DLL을 종료할 수 있을까 라는 것이겠죠? 하지만, 단순하게  UnhookWindowsHookEx 으로는 해결을 볼 수 없습니다. 그 이유는 이미 스레드 안에서 한번 그와 같은 일을 수행했기 때문이죠. 그럴때 다음과 같은 방법으로 해결 할 수 있습니다.

   - 매핑을 해제할 DLL이 발생되는 시점에, 먼저 다른 후킹을 설치합니다.
   - 리모트 스레드에게 "특별한" 메시지를 보냅니다.  
   - 후킹 프로세스에서 이 메시지를 받습니다. 이 때, call FreeLibrary 와 UnhookWindowsHookEx 호출 하는 것입니다.

지금 부터는 리모트 프로세스로(에서) DLL을 매핑하는(매핑을 해제하는) 동안에만 후킹이 사용됩니다.그 사이에 후킹된 스레드의 퍼포먼스에 아무런 영향을 끼치지 않게 됩니다. 그 다른 방법을 제시하자면, 아래에서 설명한 LoadLibrary 기술 보다 목표 프로세스에 더 이상 영향을 끼치지 않은 DLL 매핑 메카니즘을 알게 됩니다(Section II를 보세요). 하지만, LoadLibrary 기술에서 벗어난다면, WinNT와 Win9X에서 동작하는 이 기술로 적용해야 합니다.

여기서 잠깐, 이 트릭을 사용해야 하는 때는 언제 일까요? 항상 더 늦게 리모트 프로세스 안에 DLL이 생성될 때(i.e. if you subclass a control belonging to another process) and you want to interfere the target process as little as possible. I didn't use it in HookSpy because the DLL there is injected just for a moment - just long enough to get the password. I rather provided another example - HookInjEx - to demonstrate it. HookInjEx maps/unmaps a DLL into "explorer.exe", where it subclasses the Start button. More precisely: It swaps the left and right mouse clicks for the Start button.

You will find HookSpy and HookInjEx as well as their sources in the download package at the beginning of the article.

II. The CreateRemoteThread & LoadLibrary Technique
Demo application: LibSpy
In general, any process can load a DLL dynamically by using the LoadLibrary API. But, how do we force an external process to call this function? The answer is CreateRemoteThread.

Let's take a look at the declaration of the LoadLibrary and FreeLibrary APIs first:

HINSTANCE LoadLibrary(
  LPCTSTR lpLibFileName   // address of filename of library module
);

BOOL FreeLibrary(
  HMODULE hLibModule      // handle to loaded library module
);

Now, compare them with the declaration of ThreadProc - the thread routine - passed to CreateRemoteThread:

DWORD WINAPI ThreadProc(
  LPVOID lpParameter   // thread data
);

As you can see, all functions use the same calling convention and all accept a 32-bit parameter. Also, the size of the returned value is the same. In other words: We may pass a pointer to LoadLibrary/FreeLibrary as the thread routine to CreateRemoteThread.

However, there are two problems (see the description for CreateRemoteThread below):

The lpStartAddress parameter in CreateRemoteThread must represent the starting address of the thread routine in the remote process.
If lpParameter - the parameter passed to ThreadFunc - is interpreted as an ordinary 32-bit value (FreeLibrary interprets it as an HMODULE), everything is fine. However, if lpParameter is interpreted as a pointer (LoadLibraryA interprets it as a pointer to a char string), it must point to some data in the remote process.
The first problem is actually solved by itself. Both LoadLibrary and FreeLibray are functions residing in kernel32.dll. Because kernel32 is guaranteed to be present and at the same load address in every "normal" process (see Appendix A), the address of LoadLibrary/FreeLibray is the same in every process too. This ensures that a valid pointer is passed to the remote process.

The second problem is also easy to solve: Simply copy the DLL module name (needed by LoadLibrary) to the remote process via WriteProcessMemory.

So, to use the CreateRemoteThread & LoadLibrary technique, follow these steps:

Retrieve a HANDLE to the remote process (OpenProcess).
Allocate memory for the DLL name in the remote process (VirtualAllocEx).
Write the DLL name, including full path, to the allocated memory (WriteProcessMemory).
Map your DLL to the remote process via CreateRemoteThread & LoadLibrary.
Wait until the remote thread terminates (WaitForSingleObject); this is until the call to LoadLibrary returns. Put another way, the thread will terminate as soon as our DllMain (called with reason DLL_PROCESS_ATTACH) returns.
Retrieve the exit code of the remote thread (GetExitCodeThread). Note that this is the value returned by LoadLibrary, thus the base address (HMODULE) of our mapped DLL.
Free the memory allocated in Step #2 (VirtualFreeEx).
Unload the DLL from the remote process via CreateRemoteThread & FreeLibrary. Pass the HMODULE handle retreived in Step #6 to FreeLibrary (via lpParameter in CreateRemoteThread).
Note: If your injected DLL spawns any new threads, be sure they are all terminated before unloading it.
Wait until the thread terminates (WaitForSingleObject).
Also, don't forget to close all the handles once you are finished: To both threads, created in Steps #4 and #8; and the handle to the remote process, retrieved in Step #1.

Let's examine some parts of LibSpy's sources now, to see how the above steps are implemented in reality. For the sake of simplicity, error handling and unicode support are removed.

HANDLE hThread;
char    szLibPath[_MAX_PATH];  // The name of our "LibSpy.dll" module
                               // (including full path!);
void*   pLibRemote;   // The address (in the remote process) where
                      // szLibPath will be copied to;
DWORD   hLibModule;   // Base address of loaded module (==HMODULE);
HMODULE hKernel32 = ::GetModuleHandle("Kernel32");

// initialize szLibPath
//...

// 1. Allocate memory in the remote process for szLibPath
// 2. Write szLibPath to the allocated memory
pLibRemote = ::VirtualAllocEx( hProcess, NULL, sizeof(szLibPath),
                               MEM_COMMIT, PAGE_READWRITE );
::WriteProcessMemory( hProcess, pLibRemote, (void*)szLibPath,
                      sizeof(szLibPath), NULL );


// Load "LibSpy.dll" into the remote process
// (via CreateRemoteThread & LoadLibrary)
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
            (LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
                                       "LoadLibraryA" ),
             pLibRemote, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );

// Get handle of the loaded module
::GetExitCodeThread( hThread, &hLibModule );

// Clean up
::CloseHandle( hThread );
::VirtualFreeEx( hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE );
Assume our SendMessage - the code that we actually wanted to inject - was placed in DllMain (DLL_PROCESS_ATTACH), so it has already been executed by now. Then, it is time to unload the DLL from the target process:

// Unload "LibSpy.dll" from the target process
// (via CreateRemoteThread & FreeLibrary)
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
            (LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
                                       "FreeLibrary" ),
            (void*)hLibModule, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );

// Clean up
::CloseHandle( hThread );

Interprocess Communications
Until now, we only talked about how to inject the DLL to the remote process. However, in most situations the injected DLL will need to communicate with your original application in some way (recall that the DLL is mapped into some remote process now, not to our local application!). Take our Password Spy: The DLL has to know the handle to the control that actually contains the password. Obviously, this value can't be hardcoded into it at compile time. Similarly, once the DLL gets the password, it has to send it back to our application so we can display it appropriately.

Fortunately, there are many ways to deal with this situation: File Mapping, WM_COPYDATA, the Clipboard, and the sometimes very handy #pragma data_seg, to name just a few. I won't describe these techniques here because they are all well documented either in MSDN (see Interprocess Communications) or in other tutorials. Anyway, I used solely the #pragma data_seg in the LibSpy example.

You will find LibSpy and its sources in the download package at the beginning of the article.

III. The CreateRemoteThread & WriteProcessMemory Technique
Demo application: WinSpy
Another way to copy some code to another process's address space and then execute it in the context of this process involves the use of remote threads and the WriteProcessMemory API. Instead of writing a separate DLL, you copy the code to the remote process directly now - via WriteProcessMemory - and start its execution with CreateRemoteThread.

Let's take a look at the declaration of CreateRemoteThread first:

HANDLE CreateRemoteThread(
  HANDLE hProcess,        // handle to process to create thread in
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security
                                             // attributes
  DWORD dwStackSize,      // initial thread stack size, in bytes
  LPTHREAD_START_ROUTINE lpStartAddress,     // pointer to thread
                                             // function
  LPVOID lpParameter,     // argument for new thread
  DWORD dwCreationFlags,  // creation flags
  LPDWORD lpThreadId      // pointer to returned thread identifier
);

If you compare it to the declaration of CreateThread (MSDN), you will notice the following differences:

The hProcess parameter is additional in CreateRemoteThread. It is the handle to the process in which the thread is to be created.
The lpStartAddress parameter in CreateRemoteThread represents the starting address of the thread in the remote processes address space. The function must exist in the remote process, so we can't simply pass a pointer to the local ThreadFunc. We have to copy the code to the remote process first.
Similarly, the data pointed to by lpParameter must exist in the remote process, so we have to copy it there, too.
Now, we can summarize this technique in the following steps:

Retrieve a HANDLE to the remote process (OpenProces).
Allocate memory in the remote process's address space for injected data (VirtualAllocEx).
Write a copy of the initialised INJDATA structure to the allocated memory (WriteProcessMemory).
Allocate memory in the remote process's address space for injected code.
Write a copy of ThreadFunc to the allocated memory.
Start the remote copy of ThreadFunc via CreateRemoteThread.
Wait until the remote thread terminates (WaitForSingleObject).
Retrieve the result from the remote process (ReadProcessMemory or GetExitCodeThread).
Free the memory allocated in Steps #2 and #4 (VirtualFreeEx).
Close the handles retrieved in Steps #6 and #1 (CloseHandle).
Additional caveats that ThreadFunc has to obey:

ThreadFunc should not call any functions besides those in kernel32.dll and user32.dll; only kernel32 and user32 are, if present (note that user32 isn't mapped into every Win32 process!), guaranteed to be at the same load address in both the local and the target process (see Appendix A). If you need functions from other libraries, pass the addresses of LoadLibrary and GetProcAddress to the injected code, and let it go and get the rest itself. You could also use GetModuleHandle instead of LoadLibrary, if for one or another reason the debatable DLL is already mapped into the target process.
Similarly, if you want to call your own subroutines from within ThreadFunc, copy each routine to the remote process individually and supply their addresses to ThreadFunc via INJDATA.
Don't use any static strings. Rather pass all strings to ThreadFunc via INJDATA.
Why? The compiler puts all static strings into the ".data" section of an executable and only references (=pointers) remain in the code. Then, the copy of ThreadFunc in the remote process would point to something that doesn't exist (at least not in its address space).
Remove the /GZ compiler switch; it is set by default in debug builds (see Appendix B).
Either declare ThreadFunc and AfterThreadFunc as static or disable incremental linking (see Appendix C).
There must be less than a page-worth (4 Kb) of local variables in ThreadFunc (see Appendix D). Note that in debug builds some 10 bytes of the available 4 Kb are used for internal variables.
If you have a switch block with more than three case statements, either split it up like this:

switch( expression ) {
    case constant1: statement1; goto END;
    case constant2: statement2; goto END;
    case constant3: statement2; goto END;
}
switch( expression ) {
    case constant4: statement4; goto END;
    case constant5: statement5; goto END;
    case constant6: statement6; goto END;
}
END:

or modify it into an if-else if sequence (see Appendix E).

You will almost certainly crash the target process if you don't play by those rules. Just remember: Don't assume anything in the target process is at the same address as it is in your process (see Appendix F).

GetWindowTextRemote(A/W)
All the functionality you need to get the password from a "remote" edit control is encapsulated in GetWindowTextRemot(A/W):

int GetWindowTextRemoteA( HANDLE hProcess, HWND hWnd, LPSTR  lpString );
int GetWindowTextRemoteW( HANDLE hProcess, HWND hWnd, LPWSTR lpString );

Parameters
hProcess

Handle to the process the edit control belongs to.
hWnd

Handle to the edit control containing the password.
lpString

Pointer to the buffer that is to receive the text.
Return Value
The return value is the number of characters copied.

Let's examine some parts of its sources now - especially the injected data and code - to see how GetWindowTextRemote works. Again, unicode support is removed for the sake of simplicity.

INJDATA
typedef LRESULT     (WINAPI *SENDMESSAGE)(HWND,UINT,WPARAM,LPARAM);

typedef struct {    
    HWND hwnd;                    // handle to edit control
    SENDMESSAGE  fnSendMessage;   // pointer to user32!SendMessageA

    char psText[128];    // buffer that is to receive the password
} INJDATA;

INJDATA is the data structure being injected into the remote process. However, before doing so the structure's pointer to SendMessageA is initialised in our application. The dodgy thing here is that user32.dll is (if present!) always mapped to the same address in every process; thus, the address of SendMessageA is always the same, too. This ensures that a valid pointer is passed to the remote process.

ThreadFunc
static DWORD WINAPI ThreadFunc (INJDATA *pData)
{
    pData->fnSendMessage( pData->hwnd, WM_GETTEXT,    // Get password
                          sizeof(pData->psText),
                          (LPARAM)pData->psText );  
    return 0;
}

// This function marks the memory address after ThreadFunc.
// int cbCodeSize = (PBYTE) AfterThreadFunc - (PBYTE) ThreadFunc.
static void AfterThreadFunc (void)
{
}

ThradFunc is the code executed by the remote thread. Point of interest:

Note how AfterThreadFunc is used to calculate the code size of ThreadFunc. In general this isn't the best idea, because the linker is free to change order of your functions (i.e. it could place ThreadFunc behind AfterThreadFunc). However, you can be pretty sure that in small projects, like our WinSpy is, the order of your functions will be preserved. If necessary, you could also use the /ORDER linker option to help you out; or yet better: Determine the size of ThreadFunc with a dissasembler.
How to Subclass a Remote Control With this Technique
Demo application: InjectEx
Let's explain something more complicated now: How to subclass a control belonging to another process with this technique?

First of all, note that you have to copy two functions to the remote process to accomplish this task:

ThreadFunc, which actually subclasses the control in the remote process via SetWindowLong, and
NewProc, the new window procedure of the subclassed control.
However, the main problem is how to pass data to the remote NewProc. Because NewProc is a callback function and thus has to conform to specific guidelines, we can't simply pass a pointer to INJDATA to it as an argument. Fortunately, there are other ways to solve this problem (I found two), but all rely on the assembly language. So, when I tried to preserve the assembly for the appendixes until now, it won't go without it this time.

Solution 1
Observe the following picture:



Note that INJDATA is placed immediately before NewProc in the remote process? This way NewProc knows the memory location of INJDATA in the remote processes address space at compile time. More precisely: It knows the address of INJDATA relative to its own location, but that's actually all we need. Now NewProc might look like this:

static LRESULT CALLBACK NewProc(
  HWND hwnd,       // handle to window
  UINT uMsg,       // message identifier
  WPARAM wParam,   // first message parameter
  LPARAM lParam )  // second message parameter
{
    INJDATA* pData = (INJDATA*) NewProc;  // pData points to
                                          // NewProc;
    pData--;              // now pData points to INJDATA;
                          // recall that INJDATA in the remote
                          // process is immediately before NewProc;

    //-----------------------------
    // subclassing code goes here
    // ........
    //-----------------------------

    // call original window procedure;
    // fnOldProc (returned by SetWindowLong) was initialised
    // by (the remote) ThreadFunc and stored in (the remote) INJDATA;
    return pData->fnCallWindowProc( pData->fnOldProc,
                                    hwnd,uMsg,wParam,lParam );
}

However, there is still a problem. Observe the first line:

INJDATA* pData = (INJDATA*) NewProc;
This way, a hardcoded value (the memory location of the original NewProc in our process) will be arranged to pData. That is not quite what we want: The memory location of the "current" copy of NewProc in the remote process, regardless of to what location it is (NewProc) actually moved. In other words, we would need some kind of a "this pointer."

While there is no way to solve this in C/C++, it can be done with inline assembly. Consider the modified NewProc:

static LRESULT CALLBACK NewProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam ) // second message parameter
{
    // calculate location of the INJDATA struct;
    // remember that INJDATA in the remote process
    // is placed right before NewProc;
    INJDATA* pData;
    _asm {
        call    dummy
dummy:
        pop     ecx         // <- ECX contains the current EIP
        sub     ecx, 9      // <- ECX contains the address of NewProc
        mov     pData, ecx
    }
    pData--;

    //-----------------------------
    // subclassing code goes here
    // ........
    //-----------------------------

    // call original window procedure
    return pData->fnCallWindowProc( pData->fnOldProc,
                                    hwnd,uMsg,wParam,lParam );
}

So, what's going on? Virtually every processor has a special register that points to the memory location of the next instruction to be executed. That's the so-called instruction pointer, denoted EIP on 32-bit Intel and AMD processors. Because EIP is a special-purpose register, you can't access it programmatically as you can general purpose registers (EAX, EBX, etc). Put another way: There is no OpCode, with which you could address EIP and read or change its contents explicitly. However, EIP can still be changed (and is changed all the time) implicitly, by instructions such as JMP, CALL and RET. Let's, for example, explain how the subroutine CALL/RET mechanism works on 32-bit Intel and AMD processors:


When you call a subroutine (via CALL), the address of the subroutine is loaded into EIP. But, even before EIP is modified, its old value is automatically pushed onto the stack (for use later as a return instruction-pointer). At the end of a subroutine, the RET instruction automatically pops the top of the stack into EIP.
Now you know how EIP is modified via CALL and RET, but how to get its current value?
Well, remember that CALL pushes EIP onto the stack? So, in order to get its current value call a "dummy function" and pop the stack right thereafter. Let's explain the whole trick at our compiled NewProc:

Address   OpCode/Params   Decoded instruction
--------------------------------------------------
:00401000  55              push ebp            ; entry point of
                                               ; NewProc
:00401001  8BEC            mov ebp, esp
:00401003  51              push ecx
:00401004  E800000000      call 00401009       ; *a*    call dummy
:00401009  59              pop ecx             ; *b*
:0040100A  83E909          sub ecx, 00000009   ; *c*
:0040100D  894DFC          mov [ebp-04], ecx   ; mov pData, ECX
:00401010  8B45FC          mov eax, [ebp-04]
:00401013  83E814          sub eax, 00000014   ; pData--;
.....
.....
:0040102D  8BE5            mov esp, ebp
:0040102F  5D              pop ebp
:00401030  C21000          ret 0010

A dummy function call; it just jumps to the next instruction and pushes EIP onto the stack.
Pop the stack into ECX. ECX then holds EIP; this is exactly the address of the "pop ECX" instruction as well.
Note that the "distance" between the entry point of NewProc and the "pop ECX" instruction is 9 bytes; thus, to calculate the address of NewProc, subtract 9 from ECX.
This way, NewProc can always calculate its own address, regardless of to what location it is actually moved! However, be aware that the distance between the entry point of NewProc and the "pop ECX" instruction might change as you change your compiler/linker options, and is thus different in release and debug builds, too. But, the point is that you still know the exact value at compile time:

First, compile your function.
Determine the correct distance with a disassembler.
Finally, recompile with the correct distance.
That's the solution used in InjectEx. InjectEx, similarly as HookInjEx, swaps the left and right mouse clicks for the Start button.

Solution 2
Placing INJDATA right before NewProc in the remote processes address space isn't the only way to solve our problem. Consider the following variant of NewProc:

static LRESULT CALLBACK NewProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam ) // second message parameter
{
    INJDATA* pData = 0xA0B0C0D0;    // a dummy value

    //-----------------------------
    // subclassing code goes here
    // ........
    //-----------------------------

    // call original window procedure
    return pData->fnCallWindowProc( pData->fnOldProc,
                                    hwnd,uMsg,wParam,lParam );
}

Here, 0xA0B0C0D0 is just a placeholder for the real (absolute!) address of INJDATA in the remote processes address space. Recall that you can't know this address at compile time. However, you do know the location of INJDATA in the remote process right after the call to VirtualAllocEx (for INJDATA) is made.

Our NewProc could compile into something like this:

Address   OpCode/Params     Decoded instruction
--------------------------------------------------
:00401000  55                push ebp
:00401001  8BEC              mov ebp, esp
:00401003  C745FCD0C0B0A0    mov [ebp-04], A0B0C0D0
:0040100A  ...
....
....
:0040102D  8BE5              mov esp, ebp
:0040102F  5D                pop ebp
:00401030  C21000            ret 0010

Thus, its compiled code (in hexadecimal) would be: 558BECC745FCD0C0B0A0......8BE55DC21000.

Now, you would proceed as follows:

Copy INJDATA, ThreadFunc and NewProc to the target process.
Change the code of NewProc, so that pData holds the real address of INJDATA.
For example, let's say the address of INJDATA (the value returned by VirtualAllocEx) in the target process is 0x008a0000. Then you modify the code of NewProc as follows:

558BECC745FCD0C0B0A0......8BE55DC21000  <- original NewProc 1
558BECC745FC00008A00......8BE55DC21000  <- modified NewProc with real address of INJDATA

Put another way: You replace the dummy value A0B0C0D0 with the real address of INJDATA. 2
Start execution of the remote ThreadFunc, which in turn subclasses the control in the remote process.
¹ One might wonder why the addresses A0B0C0D0 and 008a0000 in the compiled code appear in reverse order. It's because Intel and AMD processors use the little-endian notation for to represent their (multi-byte) data. In other words: The low-order byte of a number is stored in memory at the lowest address, and the high-order byte at the highest address.
Imagine the word UNIX stored in four bytes. In big-endian systems, it would be stored as UNIX. In little-endian systems, it would be stored as XINU.
² Some (bad) cracks modify the code of an executable in a similar way. However, once loaded into memory, a program can't change its own code (the code resides in the ".text" section of an executable, which is write protected). Still we could modify our remote NewProc, because it was previously copied to a peace of memory with PAGE_EXECUTE_READWRITE permission.

When to use the CreateRemoteThread & WriteProcessMemory technique
The CreateRemoteThread & WriteProcessMemory technique of code injection is, when compared to the other methods, more flexible in that you don't need an additional DLL. Unfortunately, it is also more complicated and riskier than the other methods. You can (and most probably will) easily crash the remote process, as soon as something is wrong with your ThreadFunc (see Appendix F). Because debugging a remote ThreadFunc can also be a nightmare, you should use this technique only when injecting at most a few instructions. To inject a larger peace of code, use one of the methods discussed in Sections II and I.

Again, WinSpy and InjectEx, as well as their sources, can be found in the download package at the beginning of the article.

Some Final Words
At the end, let's summarize some facts we didn't mention so far:

OS Processes
I. Hooks Win9x and WinNT only processes that link with USER32.DLL1
II. CreateRemoteThread & LoadLibrary WinNT only2 all processes3, including system services4
III. CreateRemoteThread & WriteProcessMemory WinNT only all processes, including system services

Obviously you can't hook a thread that has no message queue. Also, SetWindowsHookEx wont work with system services, even if they link against USER32.DLL.
There is no CreateRemoteThread nor VirtualAllocEx on Win9x. (Actually, they can be emulated on Win9x, too; but that's a story for yet another day.)
All processes = All Win32 processes + csrss.exe
Native applications (smss.exe, os2ss.exe, autochk.exe, etc) don't use Win32 APIs, and thus don't link against kernel32.dll either. The only exception is csrss.exe, the Win32 subsystem itself. It's a native application but some of its libraries (~winsrv.dll) require Win32 DLLs, including kernel32.dll.
If you want to inject code into system services (lsass.exe, services.exe, winlogon.exe, and so on) or into csrss.exe, set the privileges of your process to "SeDebugPrivilege" (AdjustTokenPrivileges) before opening a handle to the remote process (OpenProcess).
That's almost it. There is just one more thing that you should bear in mind: Your injected code can, especially if something is wrong with it, easily pull the target process down to oblivion with it. Just remember: Power comes with responsibility!

Because many examples in this article were about passwords, you might find it interesting to read the article Super Password Spy++, written by Zhefu Zhang, too. There he explains how to get the passwords out of an Internet Explorer password field. More. He even shows you how to protect your password controls against such attacks.

Last note: The only reward someone gets for writing and publishing an article is the feedback he gets, so, if you found it useful simply drop in a comment or vote for it (). But even more importantly: Let me know if something is wrong or buggy, if you think something could be done better, or that something is still left unclear.

Acknowledgments
First, thanks to my readers at CodeGuru, where this *text* was initially published. It is mainly because of your questions, that the article grew from its initial 1200 words to what it is today: An 6000 word "animal." However, if there is someone that especially deserves to be singled out, then it is Rado Picha. Parts of the article greatly benefited from his suggestions and explanations to me. Last, but not least, thanks to Susan Moore for helping me through that minefield called the English language, and making my article more readable.

Appendices
A) Why are kernel32.dll and user32.dll always mapped to the same address?

My presumption: Because Microsoft programmers thought that it could be a useful speed optimization. Let's explain why.
In general, an executable is composed of several sections, including a ".reloc" section.

When the linker creates an EXE or DLL file, it makes an assumption about where the file will be mapped into memory. That's the so-called assumed/preferred load/base address. All the absolute addresses in the image are based on this linker assumed load address. If for whatever reason the image isn't loaded at this address, the PE - portable executable - loader has to fix all the absolute addresses in the image. That is where the ".reloc" section comes in: It contains a list of all the places in the image, where the difference between the linker assumed load address and the actual load address needs to be factored in (anyway, note that most of the instructions produced by the compiler use some kind of relative addressing; as a result, there are not as many relocations as you might think). If, on the other side, the loader is able to load the image at the linkers preferred base address, the ".reloc" section is completely ignored.

But, how do kernel32.dll, user32.dll and their load addresses fit into the story? Because every Win32 application needs kernel32.dll, and most of them need user32.dll, too, you can improve the load time of all executables by always mapping them (kernel32 and user32) to their preferred bases. Then the loader must never fix any (absolute) addresses in kernel32.dll and user32.dll.
Let's close out this discussion with the following example:

Set the image base of some App.exe to KERNEL32's (/base:"0x77e80000") or to USER32's (/base:"0x77e10000") preferred base. If App.exe doesn't import from USER32, just LoadLibrary it. Then compile App.exe and try to run it. An error box pops up ("Illegal System DLL Relocation") and App.exe fails to load.
Why? When creating a process, the loader on Win 2000, Win XP and Win 2003 checks if kernel32.dll and user32.dll (their names are hardcoded into the loader) are mapped at their preferred bases; if not, a hard error is raised. In WinNT 4 ole32.dll was also checked. In WinNT 3.51 and lower such checks were not present, so kernel32.dll and user32.dll could be anywhere. Anyway, the only module that is always at its base is ntdll.dll. The loader doesn't check it, but if ntdll.dll is not at its base, the process just can't be created.

To summarize, on WinNT 4 and higher:

DLLs, that are always mapped to their bases: kernel32.dll, user32.dll and ntdll.dll.
DLLs that are present in every Win32 application (+ csrss.exe): kernel32.dll and ntdll.dll.
The only DLL that is present in every process, even in native applications: ntdll.dll.
B) The /GZ compiler switch

In Debug builds, the /GZ compiler feature is turned on by default. You can use it to catch some errors (see the documentation for details). But what does it mean to our executable?
When /GZ is turned on, the compiler will add some additional code to every function residing in the executable, including a function call (added at the very end of every function) that verifies the ESP stack pointer hasn't changed through our function. But wait, a function call is added to ThreadFunc? That's the road to disaster. Now the remote copy of ThreadFunc will call a function that doesn't exist in the remote process (at least not at the same address).

C) Static functions Vs. Incremental linking

Incremental linking is used to shorten the linking time when building your applications. The difference between normally and incrementally linked executables is that in incrementally linked ones each function call goes through an extra JMP instruction emitted by the linker (an exception to this rule are functions declared as static!). These JMPs allow the linker to move the functions around in memory without updating all the CALL instructions that reference the function. But it's exactly this JMP that causes problems too: now ThreadFunc and AfterThreadFunc will point to the JMP instructions instead to the real code. So, when calculating the size of ThreadFunc this way: const int cbCodeSize = ((LPBYTE) AfterThreadFunc - (LPBYTE) ThreadFunc);
you will actually calculate the "distance" between the JMPs that point to ThreadFunc and AfterThreadFunc respectively (usually they will appear one right after the other; but don't count on this). Now suppose our ThreadFunc is at address 004014C0 and the accompanying JMP instruction at 00401020. :00401020   jmp  004014C0
...
:004014C0   push EBP          ; real address of ThreadFunc
:004014C1   mov  EBP, ESP
...

Then

WriteProcessMemory( .., &ThreadFunc, cbCodeSize, ..);
will copy the "JMP 004014C0" instruction (and all instructions in the range of cbCodeSize that follow it) to the remote process - not the real ThreadFunc. The first thing the remote thread will execute will be a "JMP 004014C0". Well, it will also be among its last instructions - not only to the remote thread, but to the whole process.
However, there is an exception to this JMP instruction "rule." If a function is declared as static, it will be called directly, even if linked incrementally. That's why Rule #4 says to declare ThreadFunc and AfterThreadFunc as static or disable incremental linking. (Some other aspects of incremental linking can be found in the article "Remove Fatty Deposits from Your Applications Using Our 32-bit Liposuction Tools" by Matt Pietrek)

D) Why can my ThreadFunc have only 4k of local variables?

Local variables are always stored on the stack. If a function has, say, 256 bytes of local variables, the stack pointer is decreased by 256 when entering the function (more precisely, in the functions prologue). The following function: void Dummy(void) {
    BYTE var[256];
    var[0] = 0;
    var[1] = 1;
    var[255] = 255;
}

could, for instance, compile into something like this:

:00401000   push ebp
:00401001   mov  ebp, esp
:00401003   sub  esp, 00000100           ; change ESP as storage for
                                         ; local variables is needed
:00401006   mov  byte ptr [esp], 00      ; var[0] = 0;
:0040100A   mov  byte ptr [esp+01], 01   ; var[1] = 1;
:0040100F   mov  byte ptr [esp+FF], FF   ; var[255] = 255;
:00401017   mov  esp, ebp                ; restore stack pointer
:00401019   pop  ebp
:0040101A   ret

Note how the stack pointer (ESP) was changed in the above example? But what is different if a function needs more than 4 Kb for its local variables? Well, then the stack pointer isn't changed directly. Rather, another function (a stack probe) is called, which in turn changes it appropriately. But it's exactly this additional function call that makes our ThreadFunc "corrupt," because its remote copy would call something that's not there.

Let's see what the documentation says about stack probes and the /Gs compiler option:

"The /Gssize option is an advanced feature with which you can control stack probes. A stack probe is a sequence of code that the compiler inserts into every function call. When activated, a stack probe reaches benignly into memory by the amount of space required to store the associated function's local variables.
If a function requires more than size stack space for local variables, its stack probe is activated. The default value of size is the size of one page (4 Kb for 80x86 processors). This value allows a carefully tuned interaction between an application for Win32 and the Windows NT virtual-memory manager to increase the amount of memory committed to the program stack at run time."

I'm sure one or another wondered about the above statement: "...a stack probe reaches benignly into memory...". Those compiler options (their descriptions!) are sometimes really irritating, at least until you look under the hood and see what's going on. If, for instance, a function needs 12 Kb storage for its local variables, the memory on the stack would be "allocated" (more precisely: committed) this way:

sub    esp, 0x1000    ; "allocate" first 4 Kb
test  [esp], eax      ; touches memory in order to commit a
                      ; new page (if not already committed)
sub    esp, 0x1000    ; "allocate" second 4 Kb
test  [esp], eax      ; ...
sub    esp, 0x1000
test  [esp], eax

Note how the stack pointer is changed in 4 Kb steps now and, more importantly, how the bottom of the stack is "touched" (via test) after each step. This ensures the page containing the bottom of the stack is being committed, before "allocating" (committing) another page.

After reading ..

"Each new thread receives its own stack space, consisting of both committed and reserved memory. By default, each thread uses 1 Mb of reserved memory, and one page of committed memory. The system will commit one page block from the reserved stack memory as needed." (see MSDN CreateThread > dwStackSize > "Thread Stack Size").
.. it should also be clear why the documentation about /Gs says that you get with stack probes a carefully tuned interaction between your application and the Windows NT virtual-memory manager.

Now back to our ThreadFunc and 4 Kb limit:
Although you could prevent calls to the stack probe routine with /Gs, the documentation warns you about doing so. Further, the documentation says you can turn stack probes on or off by using the #pragma check_stack directive. However, it seems this pragma doesn't affect stack probes at all (either the documentation is buggy, or I am missing some other facts?). Anyway, recall that the CreateRemoteThread & WriteProcessMemory technique should be used only when injecting small peaces of code, so your local variables should rarely *consume* more than a few bytes and thus not get even close to the 4 Kb limit.

E) Why should I split up my switch block with more than three case statements?

Again, it is easiest to explain it with an example. Consider the following function: int Dummy( int arg1 )
{
    int ret =0;

    switch( arg1 ) {
    case 1: ret = 1; break;
    case 2: ret = 2; break;
    case 3: ret = 3; break;
    case 4: ret = 0xA0B0; break;
    }
    return ret;
}

It would compile into something like this:

Address   OpCode/Params    Decoded instruction
--------------------------------------------------
                                             ; arg1 -> ECX
:00401000  8B4C2404         mov ecx, dword ptr [esp+04]
:00401004  33C0             xor eax, eax     ; EAX = 0
:00401006  49               dec ecx          ; ECX --
:00401007  83F903           cmp ecx, 00000003
:0040100A  771E             ja 0040102A

; JMP to one of the addresses in table ***
; note that ECX contains the offset
:0040100C  FF248D2C104000   jmp dword ptr [4*ecx+0040102C]

:00401013  B801000000       mov eax, 00000001   ; case 1: eax = 1;
:00401018  C3               ret
:00401019  B802000000       mov eax, 00000002   ; case 2: eax = 2;
:0040101E  C3               ret
:0040101F  B803000000       mov eax, 00000003   ; case 3: eax = 3;
:00401024  C3               ret
:00401025  B8B0A00000       mov eax, 0000A0B0   ; case 4: eax = 0xA0B0;
:0040102A  C3               ret
:0040102B  90               nop

; Address table ***
:0040102C  13104000         DWORD 00401013   ; jump to case 1
:00401030  19104000         DWORD 00401019   ; jump to case 2
:00401034  1F104000         DWORD 0040101F   ; jump to case 3
:00401038  25104000         DWORD 00401025   ; jump to case 4

Note how the switch-case was implemented?
Rather than examining every single case statement separately, an address table is created. Then, we jump to the right case by simply calculating the offset into the address table. If you think for a moment, this really is an improvement. Imagine you had a switch with 50 case statements. Without the above trick, you had to execute 50 CMP and JMP instructions to get to the last case. With the address table, on the contrary, you can jump to any case by a single table look-up. In terms of computer algorithms and time complexity: We replace an O(2n) algorithm by an O(5) one, where:

O denotes the worst-case time complexity.
We assume five instructions are neccessary to calculate the offset, do the table look-up, and finally jump to the appropriate address.
Now, one might think the above was possible only because the case constants were carefully chosen to be consecutive (1,2,3,4). Fortunately, it turns out the same solution can be applied to most real-world examples, only the offset calculation becomes somewhat more complicated. But there are two exceptions, though:

if there are three or less case statements or
if the case constants are completely unrelated to each other (i.e. "case 1", "case 13", "case 50", and "case 1000")
then the resulting code does it the long way by examining every single case constant separately, with the CMP and JMP instructions. In other words, then the resulting code is essentially the same as if you had an ordinary if-else if sequence.

Point of interest: If you ever wondered for what reason only a constant-expression can accompany a case statement, then you know why by now. In order to create the address table, this value obviously has to be known at compile time.

Now back to the problem!
Notice the JMP instruction at address 0040100C? Let's see what Intel's documentation says about the hex opcode FF:

Opcode    Instruction    Description
FF /4     JMP r/m32      Jump near, absolute indirect,
                         address given in r/m32
Oops, the debatable JMP uses some kind of absolute addressing? In other words, one of its operands (0040102C in our case) represents an absolute address. Need I say more? Now, the remote ThreadFunc would blindly think the address table for its switch is at 0040102C, JMP to a wrong place, and thus effectively crash the remote process.

F) Why does the remote process crash, anyway?

When your remote process crashes, it will always be for one of the following reasons:
You referenced a string inside of ThreadFunc that doesn't exist.
One or more instructions in ThreadFunc use absolute addressing (see Appendix E for an example).
ThreadFunc calls a function that doesn't exist (the call could be added by the compiler/linker). When you will look at ThreadFunc in dissasembler in this case you will see something like this: :004014C0    push EBP         ; entry point of ThreadFunc
:004014C1    mov EBP, ESP
...
:004014C5    call 0041550     ; this will crash the
                              ; remote process
...
:00401502    ret

If the debatable CALL was added by the compiler (because some "forbidden" switch, such as /GZ, was turned on), it will be located either somewhere at the beginning or near the end of ThreadFunc.

In any case, you can't be careful enough with the CreateRemoteThread & WriteProcessMemory technique. Especially watch for your compiler/linker options. They could easily add something to your ThreadFunc.

References:

* 관리자님에 의해서 게시물 이동되었습니다 (2005-03-09 04:09)
728x90
이 과정에서는, "Win32 어셈블리는 위대하다!!" 라고 말할 수 있는 메시지 박스를 보여 줄 수 있는 모든 함수형(functional) Windows  프로그램을 생성할 수 있습니다.  
예제 파일은 이것을 다운로드 하세요.

서론:
Windows 에서는 Windows 용 프로그램을 위한 자원들을 확보해서 준비해 놓고 있습니다 . 이러한 기능을 수행할 수 있도록 하는 핵심이 바로 윈도우즈 API(Application Programming Interface) 입니다. Windows API란, Windows 프로그램을 짤 때 사용되는 각종 유용한 함수들의 거대한 집합체를 말합니다. 이 함수들은 Dynamic-linked libraries(DLL)에  These functions are stored in several dynamic-linked libraries (DLLs) such as kernel32.dll, user32.dll and gdi32.dll. Kernel32.dll contains API functions that deal with memory and process management. User32.dll controls the user interface aspects of your program. Gdi32.dll is responsible for graphics operations. Other than "the main three", there are other DLLs that your program can use, provided you have enough information about the desired API functions.
Windows programs dynamically link to these DLLs, ie. the codes of API functions are not included in the Windows program executable file. In order for your program to know where to find the desired API functions at runtime, you have to embed that information into the executable file. The information is in import libraries. You must link your programs with the correct import libraries or they will not be able to locate API functions.
When a Windows program is loaded into memory, Windows reads the information stored in the program. That information includes the names of functions the program uses and the DLLs those functions reside in. When Windows finds such info in the program, it'll load the DLLs and perform function address fixups in the program so the calls will transfer control to the right function.
There are two categoriesof API functions: One for ANSI and the other for Unicode. The names of API functions for ANSI are postfixed with "A", eg. MessageBoxA. Those for Unicode are postfixed with "W" (for Wide Char, I think). Windows 95 natively supports ANSI and Windows NT Unicode.
We are usually familiar with ANSI strings, which are arrays of characters terminated by NULL. ANSI character is 1 byte in size. While ANSI code is sufficient for European languages, it cannot handle several oriental languages which have several thousands of unique characters. That's why UNICODE comes in. A UNICODE character is 2 bytes in size, making it possible to have 65536 unique characters in the strings.
But most of the time, you will use an include file which can determine and select the appropriate API functions for your platform. Just refer to API function names without the postfix.
Example:
I'll present the bare program skeleton below. We will flesh it out later.
.386
.model flat, stdcall
.data
.code
start:
end start

The execution starts from the first instruction immediately below the label specified after end directive. In the above skeleton, the execution will start at the first instruction immediately below start label. The execution will proceed instruction by instruction until some flow-control instructions such as jmp, jne, je, ret etc is found. Those instructions redirect the flow of execution to some other instructions. When the program needs to exit to Windows, it should call an API function, ExitProcess.

ExitProcess proto uExitCode:DWORD

The above line is called a function prototype. A function prototype defines the attributes of a function to the assembler/linker so it can do type-checking for you. The format of a function prototype is like this:

FunctionName PROTO [ParameterName]:DataType,[ParameterName]:DataType,...

In short, the name of the function followed by the keyword PROTO and then by the list of data types of the parameters,separated by commas. In the ExitProcess example above, it defines ExitProcess as a function which takes only one parameter of type DWORD. Functions prototypes are very useful when you use the high-level call syntax, invoke. You can think of invoke as a simple call with type-checking. For example, if you do:

call ExitProcess

without pushing a dword onto the stack, the assembler/linker will not be able to catch that error for you. You'll notice it later when your program crashes. But if you use:

invoke ExitProcess

The linker will inform you that you forgot to push a dword on the stack thus avoiding error. I recommend you use invoke instead of simple call. The syntax of invoke is as follows:

INVOKE  expression [,arguments]

expression can be the name of a function or it can be a function pointer. The function parameters are separated by commas.

Most of function prototypes for API functions are kept in include files. If you use hutch's MASM32, they will be in MASM32/include folder. The include files have .inc extension and the function prototypes for functions in a DLL is stored in .inc file with the same name as the DLL. For example, ExitProcess is exported by kernel32.lib so the function prototype for ExitProcess is stored in kernel32.inc.
You can also create function prototypes for your own functions.
Throughout my examples, I'll use hutch's windows.inc which you can download from http://win32asm.cjb.net

Now back to ExitProcess, uExitCode parameter is the value you want the program to return to Windows after the program terminates. You can call ExitProcess like this:

invoke ExitProcess, 0

Put that line immediately below start label, you will get a win32 program which immediately exits to Windows, but it's a valid program nonetheless.

.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:
        invoke ExitProcess,0
end start

option casemap:none tells MASM to make labels case-sensitive so ExitProcess and exitprocess are different. Note a new directive, include. This directive is followed by the name of a file you want to insert at the place the directive is. In the above example, when MASM processes the line include \masm32\include\windows.inc, it will open windows.inc which is in \MASM32\include folder and process the content of windows.inc as if you paste the content of windows.inc there. hutch's windows.inc contains definitions of constants and structures you need in win32 programming. It doesn't contain any function prototype. windows.inc is by no means comprehensive. hutch and I try to put as many constants and structures into it as possible but there are still many left to be included. It'll be constantly updated. Check out hutch's and my homepage for updates.
From windows.inc, your program got constant and structure definitions. Now for function prototypes, you need to include other include files. They are all stored in \masm32\include folder.

In our example above, we call a function exported by kernel32.dll, so we need to include the function prototypes from kernel32.dll. That file is kernel32.inc. If you open it with a text editor, you will see that it's full of function prototypes for kernel32.dll. If you don't include kernel32.inc, you can still call ExitProcess but only with simple call syntax. You won't be able to invoke the function. The point here is that: in order to invoke a function, you have to put its function prototype somewhere in the source code. In the above example, if you don't include kernel32.inc, you can define the function prototype for ExitProcess anywhere in the source code above the invoke command and it will work. The include files are there to save you the work of typing out the prototypes yourself so use them whenever you can.
Now we encounter a new directive, includelib. includelib doesn't work like include. It 's only a way to tell the assembler what import library your program uses. When the assembler sees an includelib directive, it puts a linker command into the object file so that the linker knows what import libraries your program needs to link with. You're not forced to use includelib though. You can specify the names of the import libraries in the command line of the linker but believe me, it's tedious and the command line can hold only 128 characters.

Now save the example under the name msgbox.asm. Assuming that ml.exe is in your path, assemble msgbox.asm with:

ml  /c  /coff  /Cp msgbox.asm
/c tells MASM to assemble only. Do not invoke link.exe. Most of the time, you would not want to call link.exe automatically since you may have to perform some other tasks prior to calling link.exe.
/coff tells MASM to create .obj file in COFF format. MASM uses a variation of COFF (Common Object File Format) which is used under Unix as its own object and executable file format.
/Cp tells MASM to preserve case of user identifiers. If you use hutch's MASM32 package, you may put "option casemap:none" at the head of your source code, just below .model directive to achieve the same effect.
After you successfully assemble msgbox.asm, you will get msgbox.obj. msgbox.obj is an object file. An object file is only one step away from an executable file. It contains the instructions/data in binary form. What is lacking is some fixups of addresses by the linker.
Then go on with link:

link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS  informs Link what sort of executable this program is
/LIBPATH: tells Link where the import libraries are. If you use MASM32, they will be in MASM32\lib folder.
Link reads in the object file and fixes it with addresses from the import libraries. When the process is finished you get msgbox.exe.
Now you get msgbox.exe. Go on, run it. You'll find that it does nothing. Well, we haven't put anything interesting into it yet. But it's a Windows program nonetheless. And look at its size! In my PC, it is 1,536 bytes.

Next we're going to put in a message box. Its function prototype is:

MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD

hwnd is the handle to parent window. You can think of a handle as a number that represents the window you're referrring to. Its value is not important to you. You only remember that it represents the window. When you want to do anything with the window, you must refer to it by its handle.
lpText is a pointer to the text you want to display in the client area of the message box. A pointer is really an address of something. A pointer to text string==The address of that string.
lpCaption is a pointer to the caption of the message box
uType specifies the icon and the number and type of buttons on the message box
Let's modify msgbox.asm to include the message box.
  
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib

.data
MsgBoxCaption  db "Iczelion Tutorial No.2",0
MsgBoxText       db "Win32 Assembly is Great!",0

.code
start:
invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start

Assemble and run it. You will see a message box displaying the text "Win32 Assembly is Great!".

Let's look again at the source code.
We define two zero-terminated strings in .data section. Remember that every ANSI string in Windows must be terminated by NULL (0 hexadecimal).
We use two constants, NULL and MB_OK. Those constants are documented in windows.inc. So you can refer to them by name instead of the values. This improves readability of your source code.
The addr operator is used to pass the address of a label to the function. It's valid only in the context of invoke directive. You can't use it to assign the address of a label to a register/variable, for example. You can use offset instead of addr in the above example. However, there are some differences between the two:

addr cannot handle forward reference while offset can. For example, if the label is defined somewhere further in the source code than the invoke line, addr will not work.
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial No.2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM will report error. If you use offset instead of addr in the above code snippet, MASM will assemble it happily.
addr can handle local variables while offset cannot. A local variable is only some reserved space in the stack. You will only know its address during runtime. offset is interpreted during assembly time by the assembler. So it's natural that offset won't work for local variables. addr is able to handle local variables because of the fact that the assembler checks first whether the variable referred to by addr is a global or local one. If it's a global variable, it puts the address of that variable into the object file. In this regard, it works like offset. If it's a local variable, it generates an instruction sequence like this before it actually calls the function:
lea eax, LocalVar
push eax

Since lea can determine the address of a label at runtime, this works fine

* 관리자님에 의해서 게시물 이동되었습니다 (2005-03-09 04:09)
728x90

Tutorial 1: The Basics



이 과정에서는 독자들이 기본적으로 MASM을 사용할 수 있다고 간주하고 기록하였습니다. 혹시 MASM류의 프로그램이 없다면 win32asm.exe를 다운로드 하시고, 이 과정을 수행하기 전에, 다운로드한 파일 안에 있는 문서로 학습해주시기 바랍니다. 좋습니다. 준비 되셨으면 시작하도록 하죠 ^^

서론


Win32 프로그램들은 80286 시절때 부터 계속 프로텍트 모드상에서 실행되었습니다.물론 80286은 까마득한 과거의 유산일 뿐입니다. 지금부터 우리는 80386과 그 뒤의 CPU에 대해 더 많은 관심을 가지고 분석을 할 필요가 있습니다.윈도우는 나뉘어진 가상공간상에서 각각의 Win32 프로그램들이 유기적으로 뭉쳐 돌아가는 집합체 입니다. 여기서 조금 자세히 들어가게 되면, 각 Win32 프로그램이 각자 4GB 주소 공간을 가지고 있다는 것입니다. 하지만 정확히 해야될 부분은 모든 Win32 프로그램들이 실제 메모리를 4GB씩 가지고 있다는 것이 아닙니다. 단지 그 만큼의 주소값을 가질 수 있는 범위를 의미하는 것이죠. 윈도우즈는 단지 각 프로그램이 올바른 주소값을 사용할 수 있도록 필요한 모든일을 수행하는 것 뿐입니다. 물론, 그 프로그램들은 윈도우즈에서 정한 규칙을 정확히 따라야 합니다. 그렇지 않게 되면 두려운 General Protection Fault(일반 보호 실패) 문제를 발생하게 되죠. 각 프로그램은 자신에게 살당된 각 주소 공간안에서 수행되어야 하는 것입니다. 이 부분이 Win16 시절의 형태와 명확히 구분되는 부분입니다. 모든 Win16 프로그램들은 다른 프로그램들의 주소 내용을 참조하여 볼 수 있을 만큼 구분되어진 공간이 없었습니다. 이런 특징은 어떤 프로그램도 다른 프로그램의 코드/데이터를 함부로 간섭하게 되는 문제를 줄일 수 있게 되었습니다.  
메모리 모델 또한 16비트 시절의 형태와는 전혀 다른 형태를 가지고 있습니다. Win32 하에서, 메모리 모델 또는 세그먼트(segments)등에 대해 신경쓸 필요가 없습니다. 단지 여기서는 Flat memory model 만이 있을 뿐이죠. 즉 64K의 제한적인 세그먼트는 더 이상 없습니다. 메모리는 4GB의 거대한 연속적 공간가 된 것입니다. 즉, 더이상 세그먼트 레지스터들 가지고 고민하면서 작업하실 필요가 없다는 것을 의미합니다. 메모리 공간안에 어떤 포인터를 가르키는 어떠한 세그먼트 레지스터든지 간에 모두 사용할 수 있다는 의미입니다. 이 부분은 프로그래머들에게 아주 특별한 선물일 수 밖에 없습니다. 그래서 Win32 assembly는 C처럼 쉬워지게 됩니다.
Win32하에서 프로그램을 짜게 될때, 몇가지 중요한 법칙을 아셔야 됩니다. 윈도우즈는 내부적으로 esi, edi, ebp 과 ebx를 사용ㅇ합니다. 그리고 그 레지스터들의 변화되는 값을 전혀 예상할 수 없습니다. 여러분이 지켜주셔야 될 법칙 그 하나는 바로 이것입니다. 만약 이 4가지 레지스터 중 한가지라도 여러분의 만든 Callback 함수들에서 사용된다면, 윈도우즈로 제어가 넘어가기 전에 그 레지스터들의 이전 값들을 전부 잃게 됩니다. Callback 함수라는 것은 윈도우즈에 의해 호출 되는 함수를 의미하는 것입니다. 그 쉬운 예가 윈도우즈 프로시져 입니다. 정리하자면 그 4개의 레지스터들을 가급적 사용하지 않아야 된다는 것입니다. 그래야, 정확하게 윈도우즈에게 제어권이 넘어가기전 각 레지스터의 값들이 올바르게 이전값으로 돌아 갈 수 있습니다.

내용


여기에 요점 정리형 프로그램이 있습니다. 코드가 이해가 안간다고 포기하진 마세요. 어차피 모두 나중에 설명될 내용들입니다.

.386
  .MODEL Flat, STDCALL
  .DATA
     
      ......
  .DATA?
    
     ......
  .CONST
    
     ......
  .CODE
    


That's all! Let's analyze this skeleton program.
.386

이 부분은 어셈블러의 지시자 입니다. 이부분의 의미는 80386 명령어 셋을 사용한다고 어셈블러에게 전달하는 부분입니다. 물론 .486, .586 를 사용할 수 있겠지만, .386 으로 써주는게 가장 안전합니다. 여기에는 각 CPU 모델에 맞춰서 .386/.386p, .486/.486p 와 같이 2가지의 형태로 쓸 수 있습니다. 여기서 "p" 버젼은 프로그램 안에 특별한 명령어를 사용하실 때, 꼭 필요한 내용입니다. 여기서 특수한 명령이란, 프로텍트 모드상에서 CPU/OS 상에서 예약되어져 사용되는 명령어들입니다. 그 명령들은 가상 장치 드라이버와 같은 특수한 코드에서 사용됩니다. 일반적으로 프로그램을 짤때는 p 모드를 할 필요가 없고 또 일반 모드로 사용하는게 안전한 경우가 많습니다.  
.MODEL FLAT, STDCALL

.MODEL 이라는 것도 어셈블러의 지시자 입니다. 이 지시자의 의미는 현재 작성할 프로그램의 메모리 모델을 설정하는 것입니다. Win32 상에서는 한가지 모델 밖에 없는데, 그것이 바로 FLAT 모드 입니다. 그러므로 그냥 그대로 쓰시면 됩니다.
STDCALL 은 MASM에게 파라미터 전달 집합체(parameter passing convention)의 유형을 알려주는 부분입니다. 파라미터 전달 집합체는 파라미터 전달 하는 순서를 정의하는 부분으로 왼쪽에서 오른쪽, 오른쪽에서 왼쪽으로 전달 할지를 결정합니다. 또, 함수 호출후 스택 프레임을 누가 조율 할지도 결졍하게 되는 부분입니다. 참고로 과거 Win16시절에는 C와 PASCAL의 두가지 형태의 파라미터 전달 집합체만 사용할 수 있었습니다.
  "C" 호출 방식은 오른쪽에서 왼쪽으로 전달하는 방식으로 맨 오른쪽 파라미터 내용부터 스택에 쌓이게 됩니다. 즉, 함수 호출이 끝나면 스택 프레임상의 스택 조율을 호출자가 담당하게 됩니다.  예를 들어 foo(int first_param, int second_param, int third_param) 라는 이름의 함수를 "C" 호출 방식으로 하는 방식으로 asm 코드로 만들게 되면 다음과 같습니다. :

push  [third_param]               ; Push the third parameter
push  [second_param]            ; Followed by the second
push  [first_param]                ; And the first
call    foo
add    sp, 12                                ; The caller balances the stack frame


"PASCAL" 호출 방식은 "C" 호출 방식의 반대로 생각하시면 간단합니다. 즉 파라미터 전달을 왼쪽에서 오른쪽으로 하게 되며, 함수 호출이 끝나면 스택 프레임상의 스택 조율을 피호출된 함수가 담당하게 됩니다. 기존 Win16은 "PASCAL" 호출방식으로 맞춰져 있는데, 그 이유가 코드를 더 작게 만들 수 있기 때문입니다.

"C"  호출 방식은 몇개의 파라미터가 넘겨질지 모를때 사용하기 편한데, 보통 wsprinf() 와 같은 형태의 함수에서 사용됩니다. wsprintf()의 형태에서는 실행되기 전에는 몇개의 파라미터가 있는지를 알 수 없기 때문에, 피 호출된 함수에서 함부로 스택 조율을 할 수 없게 됩니다.  
원점으로 돌아와서 STDCALL은  C 와 PASCAL 방식을 합친 방식으로 파라미터 전달을 오른쪽에서 왼쪽으로 하지만, 호출 후에 피 호출된 곳에서 스택 조율을 담당합니다. Win32 platform에서는 대부분 STDCALL 을 사용합니다. 단 예외적으로 wsprintf()만 틀립니다.. 이런 부분에선 꼭 C 호출 방식으로 사용해야 합니다.  

.DATA
.DATA?
.CONST
.CODE

총 4개의 지시자는 호출될 섹션이 어떤것인지를 알려주는 부분입니다. Win32에서는 세그먼트라는 것이 없습니다. 기억나시죠? 하지만, 전체 주소 공간에서 로직 섹션 부분을 나눌 수 있습니다. 한 섹션의 시작이란 이전 섹션과의 끝을 의미합니다. 여기에는 두가지 형태의 섹션이 있는데, 그것이 바로 데이터와 코드 입니다. 여기서 데이터 섹션은 3가지로 나뉩니다.

.DATA    이 섹션은 프로그램의 초기화된 데이터가 담는 부분입니다.
.DATA?  이 섹션은 프로그램의 초기화되지 않은 데이터를 담는 부분입니다. 가끔 미리 저장될 메모리만 잡고 그 안의 데이터는 굳이 초기화 될 필요가 없는 경우가 있습니다. 이 섹션이 바로 그런 용도로 사용됩니다. 데이터를 초기화 하지 않게 되는 경우 얻는 장점은 실행될 파일안의 공간을 차지 하지 않는다는 것입니다. 예를 들어, DATA? 섹션안에 10,000 바이트를 잡아 놓았다고 할때, 실행 테이블에서는 10,000 바이트를 안 잡습니다. 실제 메모리 크기도 마찬가지 입니다. 단지 어셈블러에게 추후 이 프로그램이 실행 되었을때 얼마만큼의 공간이 나중에 쓰일지만을 알려주는 것으로 끝납니다.
.CONST  이 섹션은 프로그램에서 사용될 상수들을 선언할 때 사용합니다. 이 섹션의 상수들의 값은 프로그램에 의해 변경되지 않습니다. 정말 그들은 *상수* 입니다.  

이 세개의 섹션은 프로그램 안에서 꼭 사용될 필요는 없습니다. 단지 필요할 때 선언하여 사용하시면 됩니다.

단 한개의 섹션 코드: .CODE. 여기에 실제 실행되는 코드가 담기게 됩니다.


< label >
end < label >


코드의 특정 범위를 결정할 때 사용되는 어떠한 이름으로 쓸 수 있는 라벨을 의미합니다. 두개의 라벨은 반드시 유일한 내용이여야 합니다.
728x90

+ Recent posts

728x90