윈도우 프로그래밍 소스 코드 분석
1.헤더 파일
#include <windows.h>
#include <stdio.h> //standard input, output = 입출력에 관한 헤더파일
1행
windows.h에는
(1) 함수 원형 = 수 많은 윈도우 API들이 선언되어 있다.
(2) 기본적인 데이터 타입이 선언되어 있다. (HWMD, MSG, LPARAM 등이 있다)
(3) 매크로 상수 등을 정의되어 있다.
(4) 윈도우 프로그래밍에 필요한 보조 헤더 파일을 포함한다.
ex) windows.h안에 winmem.h가 있어서 메모리 관련 된 헤더 파일이 안에 있다.
- 윈도우에서는 하나의 헤더 파일에 모든 API 함수들의 원형과 사용하는 상수들이 정의
- 특별한 경우에는 해당하는 헤더 파일을 포함(외부의 헤더파일 )
*표기법 = 헝가리언 표기법
2.시작점(WinMain)
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow)
- WinMain은 윈도우 프로그래밍의 시작점이다. 메모리에 로딩되면 바로 시작되는 시점이다.
→ C에서의 main() 함수를 대체한다고 볼 수 있다.
//다음은 c언어의 main() 코드이다.
int main(int argc, char argv)
보통 3개를 두는데 작성하지 않은 나머지 하나는 환경 변수에 관련된 부분이다.
이와 달리 winMain은 파라미터가 4개로 구성되어 있다.
- WinMain의 인수 값에 대해서 살펴보자
앞에 hInstance와 같이 앞에는 소문자 h가 있는 것들이 있는데 여기서 h = 핸들이다.
LPSR = long point string
int = 일반적인 숫자값
(1) hInstance
프로그램의 인스턴스 핸들이다.
*파일을 실행하면 파일이 메모리로 올라온다.
객체라는 개념에서 현실화된 것이 인스턴스라고 이야기한다.
즉, 단순히 파일에 있는 객체가 아니라 본격적으로 메모리에 올라와서 하나의 인스턴스가 되었다는 것이다.
인스턴스의 핸들을 기본적으로 커널이 구동되면 인스턴스를 인자값으로 넣어준다.
*멀티테스킹이라는 것은 메모장이 여러 개 떠있다고 하자 그러면 인스턴스가 하나가 만들어지면 나머지는 거기서 copy & paste를 해서 쓰면 된다.
(2) hPrevInstance
바로 앞에 실행된 현재 프로그램의 인스턴스 핸들이다.
없을 경우는 NULL이 되며 Win32에서는 항상 NULL이다.
16비트와의 호환성을 위해서만 존재하는 인수이므로 신경쓰지 않아도 된다.
역사가 오래되다 보니 어쩔 수 없이 생기는 일종의 찌꺼기이다.
(3) IpszCmdParam
lpsz = long point string zero(먼 거리에 있는 것을 다루기 원한다.)
z = 널문자로 끝이 난다는 이야기이다. (스트링은 널문자로 끝난다)
CmdParam = cmd에 오는 파라미터이다.
명령행으로 입력된 프로그램 인수이다.
도스의 argv 인수에 해당하며 보통 실행 직후에 열 파일의 경로가 전달된다.
씨언어에서 argv는 배열로 들어온다.
예를 들어, A.exe에 인자값으로 123을 준다고 하면 argv[0]에는 A.exe, argv[1]에는 123이 들어온다.
이와 동일한 개념이라고 할 수 있다. 하지만 씨언어에서는 123 다음에 456이 오면 argv[2]에 담아서 보내는데 CmdParam은 123와 456을 하나의 문자열로 넣는다는 차이점이 있다.
(4) nCmdShow
프로그램이 실행될 형태이며 최소화해서 보여줄 것인가 아니면, 보통 모양(일반 형태)로 띄울 것인가 등이 상수값으로 전달된다.
윈도우 프로밍이기 때문에 프로그램이 처음 실행되면 어떤 모습으로 보여줄지에 대한 것이다.
- 최종값은 return int 값이다.
3. 메시지 처리 함수 (WndProc)
LRESULT CALLBACK WndProc(HWND hWnd, UNIT iMessage, WPARAM wParam, LPARAM IParam)
CALLBACK = 콜백 함수이다. → 안써도 되긴 함
HWND hWnd = 윈도우의 핸들
UNIT iMessage = unsigned int으로 커널에서 보내온 메시지를 받는다. 하지만 메시지만 받아오는 것이 아니라 거기에서 전달된 파일도 같이 넘어와야하므로 wParam과 같은 파라미터를 따로 선언
WPARAM wParam = word로 만들어진 파라미터
LPARAM IParam = long param = 긴 형태의 파라미터가 있을 수 있기 때문에 wParam뿐만 아니라 LParam까지 2가지 형태의 파라미터를 준다.
WndProc = window procedure
msdn을 통해 어떤 메시지가 어떤 파라미터를 사용하는지 알 수 있다. (MicroSoft Developer Network)
- 사용자(키보드)와 시스템(커널)이 보내오는 메시지를 처리하는 아주 중요한 일 수행
- WinMain은 메인 윈도우를 만들고 화면에 윈도우를 표시 함
- 무한루프를 돈다. 하지만 WinMain을 실행하다가 WndProc을 호출하여 넘어가는 것을 볼 수 없다. 하지만 breakpoint을 걸어서 출력해보면 WndProc에 걸린다.
- 따라서 이러한 구조를 모르면 WinMain부터 디버깅을 하면 WndProc을 볼 수 없다. 이에 따라 Winmain부분을 보기보다는 WndProc을 주로 많이 보게 된다.
- WndProc은 대부분의 일 처리
*분석 시 가장 주의 깊게 봐야 하는 함수이다.
(찾기 어려울 수 있다 디버거를 실행하여 어디가 WinProc인지 알 수 없다. 수 많은 주소 번지 중에 찾아하기 때문에 어려울 수도 있다. )
4 . 윈도우 클래스 등록 (WinMain)
winMain 안에 있는 내용들이다.
g_hInst=hInstance; //첫번쨰 인자값으로 들어온 인자이다. g_hInst는 전역변수인데 hInstance를 나중에 사용할 수도 있기 때문에 미리 다른 곳에 저장해둔다.
//여기서부터는 윈도우를 등록하는 작업을 시작한다.
WndClass.cbClsExtra=0; //Cls는 클래스의 약자이다. 지금 윈도우 클래스를 등록하는데 이 클래스 안에 여분의 공간의미한다. 여기서는 0을 세팅되어 있기 때문에 여분의 공간이 없다고 할 수 있다.
WndClass.cdWndExtra=0;//윈도우에 관련된 정보를 받는 여분의 공간이고 0이므로 여분의 공간이 없다는 것이다.
WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);//brush handle이며 (백그라운드를 하얀색으로 하겠다).
WndClass.hCursor=LoadCursor(NULL, IDC_ARROW);// 커서로된 화살표 id값을 가져와라
WndClass.hIcon=LoadIcon(NULL, IDI_APPLICATION);//아이콘을 만드는 것이다.
WndClass.hInstance=hInstance;//인스턴스를 넣어준다.
WndClass.IpfnWndProc=WndProc; //WndProc를 정의하는 작업, 윈도우에 이벤트를 가하게 되면 WndProc을 호출하라
WndClass.IpszClassName=IpszClass; //이 클래스의 이름은 어떤 것으로 정의할지 "First"라는 문자열
WndClass.IpszMenuName=NULL; //메뉴들은 존재하지 않는다.
WndClass.style=CS_HREDRAW | CS_VREDRAW;// 윈도우 창 2개를 겹쳐지면 앞의 창에 의해 뒤에 있는 창이 가려지는데 뒤에 있는 창을 다시 클릭하면 살아나는 작업이다.
RegisterClass (&WndClass); //윈도우 클래스를 레지스터클래스로 등록시킨다.
- WNDCLASS윈도우의 스타일, 커서, 메뉴 등을 정의
- 윈도우의 특징을 부여하는 클래스
- RegisterClass()
- 정의된 WNDCLASS 값을 이용하여 윈도우를 등록한다.
[보충 설명]
- cdWndExtra란?
UX적인 측면이 강한 성격이다.
예를 들어, 메모장에 어떠한 글꼴을 적용했다. 그런데 메모를 또 다른 메모창이 있다고 하면 그 메모창에 해당 글꼴을 적용해야할지 고민될 것이다.
서로가 데이터를 공유할 수 있는 공유 공간이 필요할 것이다. 이것이 윈도우 Etra이다.
- hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
GetStockObject = 운영체제에서 어떤 것을 가져온다.
WHITE_BRUSH = 하얀색과 관련된 브러쉬를 가져와서 백그라운드에 넣어주겠다는 의미이다. (백그라운드를 하얀색으로 하겠다)
- hCursor=LoadCursor(NULL, IDC_ARROW);
IDC_ARROW = IDC는 상수이다. (ID 커서)
커서로된 화살표 ID값을 가져와라
- LoadIcon(NULL, IDI_APPLICATION)
어플리케이션에 관련된 아이콘의 id값을 가져와라
윈도우창을 띄우면 x자 같은 아이콘이 기본값으로 있는 것을 볼 수 있다.
디폴트 아이콘
- WndClass.IpfnWndProc=WndProc;
윈도우에서 이벤트가 발생하면 WndProc을 실행하는 것인데 윈도우에서 이벤트가 발생하면 WndProc을 호출하라는 것이다.
나중에 악성코드 분석할 때 WinMain에서 집중적으로 찾아야 할 부분은 이 부분이라고 할 수 있다.
-이 클래스가 동작을 하면 어디로 점프를 해서 이 함수가 실행되는지 이 함수의 위치를 찾는 것이 중요하다.
- WndClass.style=CS_HREDRAW | CS_VREDRAW;
다시 창이 살아나는 작업니다. H 는 세로, V는 가로를 의미한다.
- RegisterClass (&WndClass);
이때는 아직 등록되지는 않았고 실행을 기다리는 것이다.
이 다음 줄에 함수 createwindow, showwindow가 있을 것이다.
createwindow는 윈도우를 하나 생성해주라는 것이고 아직 화면이 보이진 않는다.
showwindow가 실행되어야 화면이 보인다.
메시지 루프
while (GetMessage(&Message, NULL, 0, 0))
{
TranslateMessage(&Message);
DispatchMessage(&Message);
}
while문을 통해 무한 루프를 돌게 될 것이다.
- GetMessage메시지 큐에서 메시지를 읽음
- 메시지를 받아와라
- TranslateMessage사용자가 입력한 키보드 입력 메시지를 가공하여 프로그램에서 쉽게 쓸 수 있도록 함
- 메시지를 해석하라
- DispatchMessage메시지 큐에서 꺼낸 메시지를 윈도우의 메시지 처리 함수 (WndProc)로 전달한다.
- 메시지를 실행하라
while문 안을 보면 WndProc으로 빠지는 부분이 명시적으로 없고 메시지가 발생하면 알아서 WndProc로 빠지는 것이다.
5. 윈도우 프로시저
Wndproc(imessage)
{
switch(imessage)
{
case WM_DISTORY: //imessage가 WM_DISTORY라면 다음과 같이 실행
PostQuitMessage(0);
return 0;
}
return ((DefWindowProc(hWnd, imessage, wParam, IParam));
}
- DefWindowProc
다양한 메시지를 사용자가 모두 처리하기에는 그 양이 방재하므로
WndProc는 DefWindowProc를 제공하여 정의하지 않은 메시지를 기본적으로 처리해준다.
이에 대한 파라미터들 (hWnd, imessage, wParam, IParam)은 WndProc에서 받은 파라미터를 그대로 넘겨준 것이라고 할 수 있다.