'Programing/프로그램 직접 개발하기(C)'에 해당되는 글 34건

  1. 2016.11.22 abex crack me 2 직접 제작하기
  2. 2016.11.20 응용기반 abex crack me 만들기 (완)
  3. 2016.11.19 콘솔기반 abex crack me 만들기
  4. 2016.11.19 공지 6

안녕하세요. 이번 포스팅에선 abex crack me 2를 직접 제작해보겠습니다.

일단 동작을 한 번 봅시다.


이름과 시리얼을 받는 칸이 있고 버튼이 달려있습니다. 이번 프로그램은 메세지박스만으로는 구현이 힘들 것 같군요. 그래서 이번엔 윈도우 창을 만들어서 구현해야 할 것 같습니다. 소스가 생각보다 복잡해지니 몇번 나눠서 포스팅 하겠습니다.

일단 제가 짜놓은 소스코드를 한 번 봅시다.

네 제법 양이 되지요? 근데 이것은 말 그대로 틀만 짜놓은 소스입니다. 자 한줄씩 분석해봅시다.

- LRESULT WINAPI WndProc(HWND, UINT, WPARAM, LPARAM);

WndProc는 WindowProc의 약자입니다. MSDN의 설명을 봅시다.

반환값이 LRESULT라고 되어있습니다. LRESULT가 대체 무슨 자료형일까요?

LRESULT의 L은 long을 뜻합니다. 그런데 대문자 L이지요? 즉 LONG를 뜻합니다. 

그러면 대체 long랑 LONG랑 뭐가 다른 걸까요?

long은 정수형 4바이트죠? 그런데 말입니다. 이 자료형의 크기가 컴파일러마다 다르게 나옵니다. 어떤 컴파일러에서는 4바이트인데 어떤 컴파일러에서는 8바이트로 나오기도 합니다. 그래서 윈도우 API에서는 이 long이란 자료형의 크기를 4바이트로 정해놓은 자료형이 따로 있습니다. 그 자료형이 LONG입니다. 그리고 이 자료형은 windows.h파일에 정의되어 있습니다. 한번 보시죠.

정의를 보시면 long을 LONG으로 정의해 놓았습니다. 크기를 한번 살펴볼까요?

제 컴파일에서는 둘다 4바이트가 나왔지만 다른 컴파일러에선 long이 8바이트가 나올 수 있습니다. 

자 Long에 대한 설명이 끝났으니 이제 본론인 LRESULT를 알아봅시다. 정의를 한번 볼까요?

LONG_PTR을 LRESULT로 정의시켜 놓았군요. LONG은 알겠는데 그 뒤의 PTR이 뭔지 모르시겠죠? 그런데 여러분. 우리 이 PTR 어디서 많이 보시지 않았습니까?

바로 리버싱 할 때 입니다. 사진을 한 번 봅시다.

DWORD PTR이라고 되어있습니다. 아니 DWORD 그냥 쓰면 될걸 왜 굳이 PTR을 달아놓았을 까요?

일단 PTR을 설명하기 전에 32비트와 64비트 환경에 대해 간단히 설명하겠습니다. 

여러분 32비트 운영체제와 64비트 운영체제의 차이점이 무엇인지 아십니까? 여러가지가 있겠지만 대표적으론 32비트는 메모리가 32비트로 사용된다는 것이고 64비트는 메모리가 64비트로 사용된다는 것입니다. 자 여기서 하나 물어봅시다. 

32비트 프로그램이 64비트에서 동작될까요? 

네 물론 동작합니다. 어떻게 하냐구요? 직접 동작시켜보았으니까요. 자 그러면 하나 더 물어보겠습니다. 

왜 동작이 가능할까요?

생각해보십시요. 32비트 프로그램은 주소를 32비트 스택에 저장시킵니다. 즉 주소값은 32비트이지요. 그런데 64비트 환경에서는 스택 하나가 64비트입니다. 즉 주소값이 64비트여야 한다는 것이지요. 64비트 환경에서 32비트 프로그램을 실행시키면 32비트 주소가 64비트 주소로 인식하겠습니까? 당연히 문제가 생깁니다.

어? 그러면 2개의 스택을 써서 주소를 저장시키면 되지 않나요? 그러면 64비트에서도 동작이 정상적으로 될 거 같은데요?

물론 2개의 스택으로 주소를 저장시키면 64비트에서도 동작합니다. 그런데? 32비트프로그램을 32비트에서 동작할 때는 어떻게 됩니까? 물론 정상적으로 실행됩니다. 근데 스택 1개로 충분한 것을 2개로 잡아서 처리하니까 메모리 효율이 극도로 떨어집니다.

이를 위해 나온것이 PTR입니다. 즉 일반 32비트 자료형 뒤에 PTR을 붙여서 32비트에서는 주소를 32비트로 사용하고 64비트에서는 주소를 64비트로 사용하게 된 것이지요.

ex) INT_PTR, DWORD_PTR, LONG_PTR

즉 이 PTR은 32비트 환경과 64비트 환경 간의 호환성을 위해 사용됩니다.

사설이 길어졌군요. 본론만 말하면 LRESULT는 LONG_PTR 즉 LONG이라고 보시면 됩니다.

CALLBACK은 설명드렸지요? __stdcall 호출 방식입니다.

자 그럼 이 WndProc 함수는 정수 4바이트를 반환하는 함수입니다. 인자를 하나씩 살펴봅시다.

* HWND 

핸들입니다. 앞서 설명드렸으니 넘어갑시다. 이 인자는 윈도우의 핸들을 받습니다.

* UINT

이 자료형의 인자는 메세지를 양의 정수로 받습니다.

* WPARAM, LPARAM

새로운 자료형이 나왔습니다. 정의를 한 번 봅시다.

UINT와 LONG이군요. WPARAM은 양의 정수이고 LPARAM은 그냥 정수네요.

WPARAM가 자료형인 인자는 메세지의 추가정보를 받습니다.

LPARAM가 자료형인 인자도 메세지의 추가정보를 받습니다.

즉 정리하면 WndProc 함수는 윈도우 창에 들어온 메세지(마우스,키 값) 등을 처리하는 함수를 뜻합니다.

윈도우 창을 띄울려고 코딩할 때에는 이 함수를 무조건 선언해줘야 합니다. 선언이 안되있으면 윈도우창을 못띄웁니다. 당연한 겁니다.

기본적으로 이 함수가 없으면 우린 윈도우창을 닫을 수가 없습니다. 마우스 위치를 입력받은 메세지를 처리 못하니까 말이죠.

지금 코드에서는 이 WndProc 함수가 선언만 되어 있습니다. 즉 틀만 구현해 놓은 것이지요. 이렇게 틀만 구현해 놓은 이유는 바로 뒤에 실행될 WinMain함수에서 사용하기 때문입니다. 

이제 WinMain을 살펴봅시다.

- Hwnd hwnd;

hwnd라는 핸들자료형을 선언합니다.

- MSG msg

msg라는 MSG자료형을 선언합니다. MSG가 무슨 자료형일까요? 물론 여러분이 생각하시는대로 message의 약자입니다. 정의를 한번 볼까요?

정의를 살펴보니 구조체로 선언되어 있습니다. 구조체 안에 여러 자료형으로 선언된 변수가 들어있네요. 즉 MSG로 선언된 변수는 저러한 멤버들을 참조할 수 있게 됩니다. 한번 볼까요?

보시다시피 멤버 6개를 참조할 수 있습니다. 각 멤버들의 설명은 나중에 하겠습니다. 일단 이 MSG가 메세지 속성 값을 저장해놓은 구조체 라는 것만 아시면 됩니다. 다음을 봅시다.

- WNDCLASS wndclass

WNDCLASS 자료형인 wndclass를 선언합니다. WNDCLASS의 정의를 한번 봅시다.

WNDLASSW로 정의되어 있네요. 뒤쪽에 W가 하나 붙었지요? 이 W는 이 함수가 유니코드로 처리되는 함수라는 것을 의미합니다. WNDCLASSW의 정의를 한번 봅시다.

구조체가 나오는 군요. 앞의 MSG랑 비슷한 맥락입니다. 이 WNDLASS는 윈도우의 속성값을 저장해 등록해놓은 구조체입니다. 자 다음의 소스를 봅시다.

 - wndclass.style = CS_HREDRAW | CS_VREDRAW;

WNDLASS의 멤버 style로 접근하여 값을 줍니다. 이 멤버 style은 윈도우가 출력되는 형태를 저장시키는 변수입니다. 그런데 값이 CS_HREDRAW와 CS_VREDRAW이군요. 

style 타입이 정수이니까 이 CS_HREDRAW와 CS_VREDRAW은 define으로 정의된 상수라는 것을 알 수 있습니다. MSDN의 설명을 봅시다.

해석을 해드리겠습니다.

CS_HREDRAW은 윈도우의 width 즉 윈도우 창의 너비가 바꼈을 경우 윈도우 창을 다시 그립니다. 

CS_VREDRAW은 윈도의 height 즉 윈도우 창의 높이가 바꼈을 경우 윈도우 창을 다시 그립니다. 

즉 style의 값을 이렇게 주었다는 것은 윈도우의 너비, 높이가 바뀌면 다시 윈도우 창을 다시 그려라 라는 뜻입니다. 중간에 | 가 있죠? 이 | 은 or을 뜻합니다.

즉 윈도우 창의 높이 혹은 너비가 바뀌면 다시 그려라 라는 뜻으로 해석이 가능합니다.

다음으로 갑시다.

 - wndclass.lpfnWndProc = WndProc;

wndclass의 멤버변수 lpfnWndProc에 WndProc를 대입하지요? 이 WndProc는 우리가 앞에 선언한 함수를 의미합니다. 그런데 뭔가 이상하지 않습니까? WndProc함수는 분명 인자가 있었습니다. 그런데 여기에서는 인자 없이 함수만 쓰였습니다. 이게 가능할까요? 예제를 한번 봅시다.

분명 구조체 result의 자료형도 int이고 sum의 반환값도 int인데 함수만으로는 저장이 안됩니다. 인자까지 다 전달해야 비로소 완벽히 저장이 되는 것이지요. 그런데 어떻게 

wndclass.lpfnWndProc = WndProc 이러한 형태가 가능한 걸까요? 정답은 lpfnWndProc의 자료형에 있습니다.

lpfnWndProc의 자료형이 WNDPROC이죠? WNDPROC의 정의를 한번 봅시다.

이 WNDPROC이란 자료형이 포인터 변수로 되어있네요. 그것도 CALLBACK 포인터 변수입니다. CALLBACK이 함수호출규약인 __stdcall이죠? 즉 __stdcall 방식으로 호출하는 함수의 주소를 저장하는 변수가 이 WNDPROC입니다.

자료형이 포인터 변수이니까 주소로 받아야 합니다. 그래서! WndProc 원형만 적어 WndProc 의 주소를 lpfnWndProc에 저장하겠다는 뜻이지요. 물론 주소도 아무 주소도 받지 않고 CALLBACK호출 방식의 함수만 저장한다는 뜻입니다.

그렇기 때문에 아무리도 리턴값과 멤버의 자료형을 똑같이 맞춰줘도 사용자가 만든 sum이란 함수는 호출방식이 __stdcall이 아니라서 위와 같은 식이 성립하지 않습니다.

즉 lpfnWndProc 멤버는 메세지처리를 하는 __stdcall 방식의 함수 주소를 저장합니다.

- wndclass.cbClsExtra = 0

클래스의 여유메모리를 뜻합니다. 즉 클래스를 처리하는 도중 특수한 경우가 발생했을 경우 이 메모리를 갖다 쓴다는 뜻입니다. 아직 우리는 여유메모리를 사용할 필요가 없으므로 0으로 주었습니다.

- wndclass.cbWndExtra = 0;

윈도우의 여유메모리를 뜻합니다. 윈도우를 처리하는 도중 특수한 경우가 발생했을 때 이 메모리를 갖다 씁니다. 우리는 0으로 줍시다.

- wndclass.hInstance = hInstance;

생성하려는 윈도우의 핸들을 저장시킵니다. 여기서 hInstance값은 WinMain의 첫번째 인자값입니다.

- wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION)

자료형이 HICON이지요? HICON의 정의를 한번 봅시다.

DECLARE_HANDLE이라는 것이 나오지요? 쉽게 얘기하면 핸들을 새로 선언한다는 뜻입니다. 즉 HICON라는 핸들을 정의한다. 라는 뜻입니다. 그러면? HWND와 무슨 차이가 있을까요?

별 차이 없습니다. HWND은 핸들 윈도우, HICON은 핸들 아이콘입니다. 즉

HICON은 아이콘의 핸들을 받는다는 뜻입니다.

그렇다면 Icon멤버는 윈도우의 아이콘을 저장하는 멤버라는 것을 알 수 있습니다. 

자 이제 LoadIcon에 대해 알아봅시다. MSDN의 설명을 봅시다.

반환값이 HICON입니다. 즉 아이콘 핸들을 반환하다는 뜻입니다. 인자를 살펴봅시다.

 * HINSTANCE

아이콘의 핸들을 받습니다. 만약 받지 않을 시 NULL을 지정합니다. 저는 일단 NULL값을 주었습니다. 나중에 바꿀 것입니다.

* lpIconName

자료형이 LPCTSTR이므로 아이콘 이름을 유니코드 문자열로 받습니다. 여기서는 

IDI_APPLICATION을 넣었지요? 정의를 확인해봅시다.

응용프로그램의 기본 아이콘이라고 정의되어 있습니다.

자 다음으로 갑시다.

- wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

HCURSOR 자료형은 HICON이랑 비슷합니다. 정의를 살펴봅시다.

HICON이랑 같다고 나옵니다. 즉 이름만 다르고 핸들을 받는 것은 똑같다는 것입니다.

그러면 HCURSOR은 어떤 핸들을 받냐고 하니 마우스 커서를 핸들로 받습니다.

LoadCursor함수를 살펴봅시다.

구조가 HICON이랑 거의 똑같습니다.  커서의 핸들과 커서이름을 인자로 받아 핸들을 반환합니다. 커서이름인자 부분에 IDC_ARROW라고 적혀있지요? IDC_ARROW의 정의를 봅시다.

표준 화살표라고 되어있네요. 다음으로 넘어갑시다.

- wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

hbrBackground의 자료형은 HBRUSH입니다. 정의를 살펴봅시다.

그냥 정의된 핸들이군요. 이 HBRUSH는 브러쉬 핸들을 뜻합니다. 즉 브러쉬핸들을 저장하겠다 라는 뜻이지요. 

자 값을 넣는 쪽을 보면 GetStockObject함수가 쓰였습니다. 한번 살펴봅시다.

반환값이 HGDIOBJ 자료형입니다. 이 HGDIOBJ는 Handle GDI 오브젝트를 뜻합니다.

여기서 GDI란 그래픽 디바이스 인터페이스의 약자로 그래픽을 도와주는 장치들을 모아놓은 집합을 의미합니다. 여기서 장치는 함수,자료형 등 일 것이고 오브젝트는 객체라는 뜻을 가지고 있으므로 HGDIOBJ란 그래픽 관련 함수와 자료형 등 을 모아놓은 객체라고 보시면 됩니다. 

그래서 이 객체에는 여러가지 자료형을 포함하고 있습니다.(HGDIOBJ, HPEN 등)

그런데 우리가 받아야 할 자료형은 hbrBackground의 자료형인 HBRUSH이죠? 그래서 HGDIOBJ에서 HBRUSH만 뽑아오도록 형변환을 시켜준 것입니다.

자 이제 하나 있는 인자를 살펴봅시다.

fnObject라고 되어있는데 이 자리에 제가 WHITE_BRUSH를 적었지요? 정의된 상수입니다. 목록을 한 번 봅시다.

하얀색 브러쉬를 뜻합니다. 즉 여기서는 하얀색 브러쉬를 인자로 받아 브러쉬 핸들을 넘겨준다는 것이지요. 여기서 brush는 그냥 배경화면이라고 보시면 됩니다.

- wndclass.lpszMenuName = NULL;

메뉴 이름을 저장하는 멤버입니다. 우리는 아직 메뉴가 필요없으니 NULL로 주었습니다.

- wndclass.lpszClassName = L"HELLOWINDOWS";

lpszClassName의 자료형은 LPCTSTR입니다. 즉 유니코드 상수 문자열을 받습니다.

이 멤버는 윈도우의 클래스 이름을 저장합니다. 여기서 윈도우 클래스 이름은 윈도우를 의미하는 변수라고 보시면 됩니다. 나중에 이 윈도우를 생성시킬 때 이 이름을 사용하니 잘 기억해 두셔야 합니다.

여기까지가 wndclass의 멤버에 값을 등록하는 거였습니다. 참고로 저 멤버중 하나라도 빠지면 정상 동작안합니다. 하나도 빠짐 없이 입력해 주세요.

- RegisterClass(&wndclass);

RegisterClass함수는 WNDCLASS로 선언된 윈도우 클래스를 등록하겠다는 것입니다. 이렇게 등록된 윈도우클래스는 나중에 윈도우 창을 생성시킬 때 사용합니다.

MSDN의 정의를 봅시다.

WNDCLASS로 선언한 윈도우 클래스의 주소를 인자로 받습니다. 반환형이 ATOM이네요. ATOM의 정의를 봅시다.

WORD이군요. 즉 윈도우 클래스의 주소를 2바이트로 넘긴다는 뜻입니다.

다음으로 넘어갑시다.

- hWnd = CreateWindow(wndclass.lpszClassName, L"Hello Windows Application",

        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,

        CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, wndclass.hInstance, NULL);

드디어 대망의 CreateWindow함수가 나왔습니다. 여태까지 작성한 윈도우 클래스를 기반으로 윈도우 창을 만드는 것입니다. MSDN의 정의를 봅시다.

반환값은 핸들입니다. 인자들을 하나씩 살펴 봅시다.

* lpClassName

클래스 이름을 뜻합니다. 아까 우리가 만든 wndclass.lpszClassName의 값이 들어갑니다.

* lpWindowName

윈도우 이름을 인수로 받습니다. 여기서 받는 이름은 윈도우 창이 생성될 때의 제목이 됩니다.

* dwStyle

윈도우 창의 스타일을 인수로 받습니다. 여기서  쓰인 값은 

WS_OVERLAPPEDWINDOW값입니다. 정의를 한 번 봅시다.

이 윈도우창은 중복된 윈도우창이라는군요. 이것이 무슨 소리냐 하면 

WS_OVERLAPPEDWINDOW 값은 밑의 윈도우 창을 나타내는 6개의 속성을 다 포함한다는 뜻입니다. 즉 WS_OVERLAPPEDWINDOW를 써놓으면 갖출건 다 갖춘 윈도우 창 을 만들 수 있다는 것입니다. 이것은 나중에 자세히 말씀드리겠습니다.

* x

윈도우 창을 처음 생성할 때의 x좌표 값을 의미합니다. 여기서 CW_USEDEFAULT값을 설정해주면 윈도우 창이 생성될 때 OS에서 기본값 x를 설정해 줍니다.

* y

윈도우 창을 처음 생성할 때의 y좌표 값을 의미합니다. 여기서 CW_USEDEFAULT값을 설정해주면 윈도우 창이 생성될 때 OS에서 기본값 y를 설정해 줍니다.

* nWidth

윈도우 창을 처음 생성할 때 창의 너비를 뜻합니다. 여기서 CW_USEDEFAULT값을 설정해주면 윈도우 창이 생성될 때 OS에서 기본값 너비를 설정해줍니다.

* nHeight

윈도우 창을 처음 생성할 때 창의 높이를 뜻합니다. 여기서 CW_USEDEFAULT값을 설정해주면 윈도우 창이 생성될 때 OS에서 기본값 높이를 설정해줍니다.

* hWndParent

부모 윈도우 핸들을 의미합니다. 아직 우리는 부모 윈도우 창이 없으므로 일단 NULL값을 주었습니다. 이것은 나중에 설명드리겠습니다.

* hMenu

자료형이 HMENU입니다. 정의를 한 번 봅시다.

반환은 메뉴핸들로 반환하는 군요. 즉 메뉴 핸들을 인자로 받습니다. 아직 우리는 메뉴를 사용하지 않을 것이므로 NULL값을 주었습니다.

* hInstance

생성할 윈도우의 핸들을 의미합니다. 여기선 우리가 정의해놓은 윈도우클래스의 핸들을 가지고 옵니다.

* lpParm

이 인자의 자료형은 LPVOID입니다. 정의를 봅시다.

반환값이 없는 포인터 함수를 의미하는군요. 여기서 이 인자는 생성 윈도우 정보를 받습니다. 이것은 나중에 자세히 알려드리겠습니다. 일단 NULL로 주었습니다.

자 이것으로 CreateWindow 함수 설명을 다 끝냈습니다. 이 함수의 결과값은 핸들이므로 우리가 선언한 hwnd에 들어갈 것입니다. 다음으로 넘어갑시다.

 - ShowWindow(hWnd, nCmdShow);

생성한 윈도우를 화면에 나타내주는 함수입니다. 설명을 봅시다.

반환값은 BOOL 이므로 참, 거짓 둘중 하나입니다. 자 인자들을 하나씩 살펴봅시다.

* hwnd

생성한 윈도우 핸들입니다. CreateWindow의 핸들값이 들어있는 hwnd를 넣어주었습니다.

* nCmdShow

윈도우창을 화면에 띄울 때 어떤 방식으로 띄울지를 받습니다. 우리는 WinMain의 매개변수를 주었습니다. 보통 WinMain의 매개변수를 인자로 주면 기본 설정은 SW_SHOW가 됩니다. 정의를 한 번 봅시다.

정해진 크기와 위치에 윈도우 창을 활성화 시킵니다.

- while (GetMessage(&msg, NULL, 0, 0))

    {

        TranslateMessage(&msg);

        DispatchMessage(&msg);

    }

while은 반복이지요. 무엇을 반복한다는 것일까요? 조건문에 적힌 GetMessage를 살펴봅시다.

반환형은 참, 거짓입니다. 이 GetMessage함수는 말그대로 메세지를 받아옵니다. 인자들을 살펴봅시다.

* lpMsg

자료형이 LPMSG입니다. 정의를 한번 봅시다.

MSG구조체의 포인터를 의미하는 자료형이군요. 즉 MSG구조체의 주소값을 받습니다.

* hwnd

메세지가 발생한 윈도우의 핸들을 의미합니다. 아직 그런 윈도우가 없으므로 NULL을 줍시다.

* wMsgFilterMin

이 인자는 최소정수값을 의미합니다. 이 함수에서 메세지를 받을 때에는 정수형으로 받습니다. 그런데 메세지 번호가 한 두개 이겠습니까? 아니지요?  즉 필요한 메세지만 받기 위하여 최소 정수값을 인자로 받습니다.

* wMsgFilterMAX

위의 인자가 최소정수값을 받는다면 이 인자는 최대정수값을 받습니다.

추가로 이 두 인자가 둘다 0일 경우에는 메세지를 필터링하지 않고 모두 받습니다. 

자 이제 GetMessage 함수 설명이 끝났습니다. 그러면 이러한 GetMessage 함수를 왜 반복문에 돌리는 것일까요?

일반적으로 Windows GUI 응용프로그램은 실행된 후에 단지 윈도우를 출력할 뿐이며일반적으로 아무것도 하지 않습니다. 이런 프로그램은 사용자가 키보드 입력이나 마우스 버튼 클릭으로 이벤트가 발생되면 그때마다 대응되는 처리를 하는 방식이기 때문이지요.

그럼 그 이벤트를 어떻게 처리를 하느냐고 하니 컴퓨터의 장치 중 하나가 이를 감지하여 응용프로그램에 넘겨줍니다. 이 때 그 이벤트는 응용프로그램의 큐에 저장이 되는데 이 큐에 저장된 이벤트를 메세지라고 합니다. 참고로 큐는 스택의 반대개념이라고 보시면 됩니다. 이렇게 저장된 메세지는 GetMessage함수에서 받아서 처리를 하게 됩니다.

메세지는 끊임없이 받아야 하므로 반복문에 GetMessage함수를 넣어 처리를 하도록 하는 것입니다. 언제까지 처리하냐구요? 생성된 윈도우가 종료될 때까지 입니다.

정리하면 이 GetMessage함수는 메세지를 받아서 이 메세지가 WM_QUIT라면 FALSE 아니라면 TRUE를 반환합니다. 

자 이제 반복문 안에 있는 함수를 살펴봅시다.

 - TranslateMessage(&msg);

TranslateMessage함수의 정의를 봅시다.

인자는 MSG의 주소를 받습니다. 예도 GetMessage함수와 비슷합니다. 하지만 이 함수는 메세지를 인자로 받았을 때 이 메세지가 키보드 관련 메세지일 경우 이 키보드 관련 메세지에서 추가적인 정보를 뽑아내 반환합니다. 즉 a라는 키를 입력한 메세지를 인자로 받았을 경우 이 메세지에서 ctrl 등의 키가 눌러졌는지 안눌러졌는지에 대한 추가 정보를 뽑아내서 메세지에 덧붙입니다.

즉 키보드 관련 메세지를 받아서 처리할 경우에만 TRUE를 반환하고 그 외의 메세지를 받았을 경우에는 FALSE를 반환합니다.

- DispatchMessage(&msg);

정의를 한 번 봅시다.

이 함수도 메세지를 인자로 받아 LRESULT 형식으로 반환합니다. 즉 정수형으로 반환하는 것이지요.

이 함수는 간단하게 얘기하면 받은 메세지를 msg의 멤버에 저장시켜 LRESULT 값으로 윈도우 프로시저에 전달시킵니다. 여기서 윈도우 프로시저란 WndProc함수를 말합니다.

최종적으로 정리하면

while (GetMessage(&msg, NULL, 0, 0))

    {

        TranslateMessage(&msg);

        DispatchMessage(&msg);

    }

는 윈도우 창이 생성된 직후 큐에 저장된 메세지를 읽어들여 처리하는 부분입니다.

즉 GetMessage 함수로 메세지를 읽어오고 TranslateMessage 함수로 키보드 관련 메세지를 처리한 후 DispatchMessage 함수로 인해  WndProc함수에 전달됩니다.

이 함수가 없으면 윈도우 창이 생성된 후 추가적으로 오는 메세지를 받지 못해 바로 종료가 되버립니다. 궁금하시분 들은 이 while을 주석처리하고 실행해보십시요.

여기까지가 WinMain의 설명입니다. 너무 많지요? 근데 아직 덜 끝났습니다. 조금만 더 힘내봅시다. 이제 앞에서 선언된 WndProc함수를 구현해줘야 합니다. WndProc함수의 내부를 봅시다.

- switch (message)

    {

    case WM_DESTROY:

        PostQuitMessage(0);

    }

return DefWindowProc(hWnd, message, wParam, lParam);

switch문은 다들 아시죠? 정수를 받아서 해당되는 case문을 실행하는 명령어입니다.

여기서는 message를 받습니다. 이 message는 DispatchMessage 함수에서 받아온 메세지를 받는 매개변수입니다. (2번 째 인자) case문을 살펴봅시다.

* WM_DESTROY

윈도우 창이 파괴되었다는 것을 알리는 메세지입니다. DispatchMessage에서 받은 메세지가 WM_DESTROY라면 PostQuitMessage함수를 호출합니다. PostQuitMessage의 정의를 봅시다.

이 함수는 WM_QUIT라는 메세지를 큐에 저장시킵니다. 즉 종료하라는 메세지를 저장시키는 거지요. 이 함수 인자는 종료코드를 의미하는데 지금은 쓸 일이 없으므로 0을 주었습니다.

이 함수로 인해 큐에 WM_QUIT 메세지가 저장되고 나중에 GetMessage에서 WM_QUIT를 받으면 리턴값을 FALSE로 반환하고 이로 인해 반복문을 빠져나가서 return 0을 실행하고 윈도우 프로그램이 정상적으로 종료됩니다.

- return DefWindowProc(hWnd, message, wParam, lParam);

DefWindowProc함수의 정의를 봅시다.

WndProc함수랑 똑같은 인자와 반환값이다. 그러면 대체 DefWindowProc랑 WndProc의 다른 점이 뭘까요? 

DefWindowProc 의 Def는 Default입니다. 즉 기본을 의미하지요. 보통 WndProc에서 처리되지 않은 메세지를 자동으로 처리할 때 쓰입니다.

우리가 작성한 WndProc에서는 WM_DESTROY메세지만 처리가 되었지요? 그런데 그 외의 메세지도 있을 것이 아닙니까. 그러한 메세지를 자동으로 처리해 반환하도록 DefWindowProc함수를 사용하였습니다.

제가 작성한 소크코드 설명이 전부 끝났군요. 창 하나띄우는데 정말 많은 코드가 사용되었습니다. 이제 작성한 코드를 실행시켜 봅시다.

하얀색 배경의 창이 띄워졌습니다. 그런데 우리가 만들려는 abex crack me 2랑 너무 다르지요? 그래서 다음포스팅에서는 이 소스코드를 기반으로 abex crack me2 랑 비슷하게 만들어보겠습니다.

이상으로 포스팅을 마치겠습니다.







Posted by englishmath
,

안녕하세요. 저번에 공지한대로 이번 포스팅에서는 콘솔창을 띄우지 않고 프로그래밍을 해보겠습니다.

visual stdio를 열어 프로젝트를 만듭니다.

win32 응용 프로그램을 만들기 위해 Win32 프로젝트를 선택하여 프로젝트를 만듭니다.

만들 프로그램을 windows 응용 프로그램으로 선택하고 빈 프로젝트를 체크해 준 다음 마침을 누릅시다. 가끔 사람들이 빈 프로젝트가 뭐냐고 물으시는 분 들이 있는데 말 그대로 비어있는 프로젝트를 뜻합니다.

빈 프로젝트를 체크해제하고 만들 시 visual stdio에서 지정한 기본 main 함수가 미리 적혀있는 것을 볼 수 있습니다.

아래는 빈 프로젝트를 체크해제하고 만든 프로젝트입니다.

보시다시피 상당한 지식을 요구하는 코드가 자동으로 적혀있는 것을 볼 수 있습니다. 즉 전문가용 옵션입니다. 우리는 전문가가 아니니까 그냥 빈 프로젝트 옵션을 사용합시다.

프로젝트를 만드셨으면 소스를 만듭시다.

c언어로 작성해야하니 c++ 파일을 선택하시고 소스파일의 확장자를 c로 맞춥시다.

여기까지 하셨으면 이제 소스를 작성할 차례입니다. 미리 제가 작성한 소스를 한 번 봅시다.

네 소스코드는 콘솔프로그래밍과 별 차이가 없습니다. 하지만

main부분이 상당히 다르다는 것을 느낄 수 있습니다. 

- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow)

이번 포스팅의 핵심부분입니다. 하나씩 살펴봅시다.

*WINAPI WinMain

WINAPI란 windows api를 뜻합니다. 즉 windows에서 사용하는 api라는 뜻입니다.

여기서 api가 무엇인고하니 api는 Application Programming Interface의 약자를 뜻합니다.

해석하면 응용프로그램 프로그래밍 인터페이스를 뜻하는데 인터페이스가 두 장치 간의 상호작용을 위한 매개체를 뜻하므로 풀이하면

응용프로그램을 프로그래밍하기 위한 매개체를 뜻합니다. 쉽게 말하면 응용프로그램을 만들기 쉽게 해주는 규격 같은 것입니다.

즉 winapi는 윈도우에서 응용프로그램을 프로그래밍할 때 도움을 주는 인터페이스를 의미합니다.

정리하면 WINAPI WinMain이란 WINAPI 호출규약인 WinMain함수를 선언하였다. 라고 보시면 됩니다. 일반 콘솔 프로그래밍에서는 컴파일러가 main함수를 찾는 것이 비해 응용 프로그래밍에서는 컴파일러가 WinMain함수를 찾습니다. 그러므로 WinMain함수를 선언해 주었습니다.

이러한 WinMain은 인자를 다음과 같이 받습니다.

보시게 되면 제가 WINAPI를 넣었던 자리에 CALLBACK라고 적혀있는 것을 볼 수 있습니다. 사실 이자리는 함수호출규약을 정하는 자리입니다. 

함수호출규약이 무엇이냐면 함수를 호출할 때 스택을 정리하는 규약을 정해놓은 것입니다. 일반적으로 __cdecl와 __stdcall가 있는데 일반 콘솔 프로그래밍에서 함수를 호출 할 경우에는 __cdecl를 쓰지만 응용프로그래밍을 할 경우에는 __stdcall 방식을 씁니다. 좀더 자세히 알아보기 위해 실습을 해 봅시다.

WinDef.h를 작성하시고 문서를 열어봅시다. WinDef 헤더파일은 여러 자료형을 정의해 놓은 문서입니다.

문서를 찾아보면 위와 같이 정의되 있는 곳을 찾을 수 있습니다.

MSDN의 CALLBACK과 제가 작성한 WINAPI의 정의된 값을 보면 __stdcall라고 정의되어있습니다. 즉 함수호출규약을 뜻합니다.

즉 CALLBACK 자리에는 CALLBACK이 와도 되고 WINAPI도 와도 되고 __stdcall이 와도 된다는 것입니다. 

그러면 꼭 함수 호출 규약을 적어야 하나? 라는 의문이 들 수 있습니다. 실험 결과 규약을 적지 않아도 프로그램이 정상적으로 실행되는 것을 볼 수 있었습니다. 하지만 밑의 결과를 보면

WinMain의 호출규약은 __stdcall이어야 한다고 경고가 나옵니다.(에러 아닙니다)

그래서 저는 저 자리에 WINAPI를 적어주었습니다.

이제 인자들을 차례대로 살펴 봅시다.

hInstance

자료형이 HINSTANCE입니다. HINSTANCE는 핸들 인스턴스의 약자이며 쉽게 말하면 프로그램 자체의 주소라고 보시면 됩니다. HWND가 프로그램 내의 윈도창의 번호라고 한다면 HINSTANCE는 프로그램의 번호라고 보시면 됩니다.

hPrevInstance

자료형이 HINSTANCE입니다. 즉 프로그램 자체의 주소를 받는다는 것은 앞의 인자와 동일하나 이 인자는 이 프로그램의 주소가 아닌 앞에 실행된 프로그램의 주소를 받습니다. 그런데 이 인자는 16비트 환경일 때 사용한 인자이며 현재 32비트 환경에서는 사용되지 않습니다. 그래서 기본적으로 NULL값이 인자로 들어갑니다.

*lpCmdLine

자료형이 LPSTR인 이 인수는 이 프로그램의 이름 혹은 경로를 유니코드체계로 받습니다.

*nCmdShow

자료형이 INT인 이 인수는 윈도우가 표시되는 방법을 정수형으로 받습니다.

자 여기까지가 WinMain의 설명입니다. 기본적으로 WinMain를 사용할 시에는 저 4개의 인자를 적어주어야 합니다.

추가로 덧붙이자면 위의 인자이름들은 가독성을 위해 MSDN에서 정해놓은 인자이므로 굳이 인자이름을 똑같이 적을 필요는 없습니다.

자료형만 맞추고 인자 이름이 예약어가 아니라면 정상적으로 실행이 됩니다.

자 이제 소스설명이 끝났으니 한번 실행 해 봅시다.

콘솔창이 뜨지 않는 것으로 보아 정상적으로 실행됐음을 알 수 있습니다.

다음 포스팅은 이렇게 제작한 프로그램을 리버싱하는 것을 올리겠습니다. 참고로 리버싱 카테고리에 올릴 예정입니다.

이상으로 포스팅을 마치겠습니다.





Posted by englishmath
,

안녕하십니까? 이번 포스팅의 주제는 abex crack me 만들기 입니다.

abex crack me의 동작화면을 한 번 보겠습니다.

간단하게 메세지박스 2개가 출력되는 프로그램입니다.

그럼 이것을 직접 c로 구현해보겠습니다.

저는 visual studio로 작성하겠습니다.

(참고로 저는 visual studio 2010 express 입니다.)


자 소스를 이렇게 작성하였습니다. 한줄씩 설명해드리겠습니다.

-  #include <windows.h> 

windows.h 헤더파일을 선언합니다. 메세지를 띄우기 위한    MessageBox함수와 GetDriveType함수를 사용하기 위해서입니다.

-  int EAX,ESI = 0 

연산에 필요한 변수 EAX와 ESI를 선언하였습니다.

- MessageBox (NULL, L"Make me think your HD is a CD-Rom.", L"abex' 1st crackme", MB_OK);

MessageBox함수입니다. 말 그대로 메세지박스를 띄우기 위한 함수입니다. 자세히 알아보기 위해 MSDN을 참고하겠습니다.

인자가 총 4개입니다. 하나하나씩 살펴 봅시다.

 * hWnd

  첫번 째 인자인 hWnd는 자료형이 HWND입니다. 이 HWND라는 것이 핸들윈도우의 약자를 뜻하는데 이 자료형으로 선언된 변수는 윈도우 창을 식별할 때에 쓰입니다. 

사실 이것을 정확하게 이해할려면 handle에 대한 개념을 알아야 하는데 너무 복잡해지니 그냥 넘어갑시다.

아무튼 결론만 말하면 이 hwnd는 메세지 상자의 소유자에 대한 핸들을 뜻합니다. 우리는 딱히 소유자를 정할 필요가 없으니 NULL값을 주었습니다.

* lpText

앞의 글자는 대문자 I가 아닌 소문자 L입니다. 유념하시기 바랍니다.

이 인자는 메세지창에 표시되는 문자열을 뜻합니다. 그런데? 자료형이 LPCTSTR이죠?

이 것을 간단하게 설명해드리겠습니다.

문자열을 저장하는 자료형이 char인것은 잘 아시죠? 그런데 사실 문자열을 저장하는 자료형이 제법 많습니다.

char, wchar, tchar, LPSTR, LPCSTR, LPCTSTR 등이 있습니다..

다른 자료형도 설명해주면 좋겠지만 그렇게 되면 너무 복잡해지므로 LPCTSTR만 설명하겠습니다.

먼저 LPCTSTR을 설명하기 전에 코드를 잠깐 설명하겠습니다.

코드는 크게 3종류로 나뉩니다.

싱글바이트코드, 멀티바이트코드, 유니코드로 나뉩니다.

싱글바이트 코드는 다른 말로 아스키코드라고도 하며 1바이트로 표현 가능한 문자들을 모아놓은 코드 체계입니다.

멀티바이트코드는 아스키코드문자 집합에 2바이트로 표현가능한 특정 문자를 넣은 코드체계입니다. ex)한글 등

유니코드는 전 세계의 문자를 2바이트로 표현가능하도록 규정해 놓은 코드체계입니다.

여러 코드가 나왔는데 이러한 코드를 설명한 이유는 이 코드 체계를 사용하는 자료형이 따로 나뉘어져 있기 때문입니다. 아래를 살펴보시면

싱글코드 체계를 사용하는 자료형

- char 등

멀티바이트 코드체계를 사용하는 자료형

- char *, const char * 등

유니코드 체계를 사용하는 자료형

- wchar_t, wchar_t *, TCHAR, LPSTR, LPCSTR, LPCTSTR 등

자 여기까지 읽으셨으면 우리가 찾는 LPCTSTR이 유니코드 체계를 사용하는 자료형이라는 것을 알 수 있습니다. 여기서 세부 설명을 하면

LP는 long pointer 즉 *를 의미하고 C는 const, T는 TCHAR의 T, STR은 String 입니다.

즉 LPCTSTR은 const TCHAR * 라고 생각하시면 됩니다. const char * 이 멀티바이트 코드체계를 사용하는 것에 비해 LPCTSTR은 유니코드체계를 사용합니다.

너무 이야기가 길어졌군요.  결론만 얘기하면 자료형이 LPCTSTR인 인자는 const char* 형식으로 값을 넣을 수 없다는 것입니다. 

이렇게 const char* 식으로 값을 넣으면 에러가 뜹니다. 그럼 어떻게 해야 할까요?

이럴때 대문자 L을 사용합니다. 즉 L"문자열" 이렇게 사용합니다.

L이 무엇이냐면 선언입니다.

즉 L 뒤에 오는 문자열이 유니코드 문자열이다 하고 선언해주는 것이지요.

그냥 글자를 적으면 이 글자가 멀티바이트코드체계 문자열인지 유니코드체계 문자열인지 컴퓨터가 인식을 하지 못합니다. 그래서 문자열 앞에 L을 붙임으로써 이 문자열이 유니코드체계문자열임을 선언하겠다라는 뜻입니다.

다음 인자로 넘어갑시다.

* lpCation 

  자료형이 LPCTSTR인 이 인자는 메세지 창의 제목을 뜻합니다. LPCTSTR은 설명했으니 넘어갑시다.

* uType

자료형이 UINT입니다. INT랑 비슷한 자료형인데 INT는 음수까지 저장가능한 것에 비해 UINT는 양수만 저장가능합니다. 하지만 INT보다 많은 범위의 양수를 저장할 수 있습니다.

여기 인자는 메세지에 표시되는 버튼을 뜻합니다. 여기서는 확인 버튼만 필요하므로 이미 정의되어 있는 MB_OK 라는 인자를 사용하였습니다. 

길고 긴 Messagebox 함수 설명이 끝났군요. 혹시 따로 궁금하신 분이 있다면 댓글에 질문을 해주시면 답변드리겠습니다.

- EAX = GetDriveType(L"C:\\");

GetDriveType은 현 컴퓨터의 디스크 드라이브 타입을 가르쳐주는 함수입니다.

MSDN의 설명을 봅시다.

인자가 하나 뿐이군요. lpRootPathName입니다. 자료형은 LPCTSTR인데 설명했으니 넘어갑시다. 

이 인자가 받는 값은 루트 경로를 뜻합니다. 즉 드라이브의 루트 디렉토리 경로를 인자로 받습니다. C드라이브의 루트 디렉토리가 어딜까요? 물론 다들 아시겠지만 확인을 위해 C디스크 안의 아무 폴더나 들어가봅시다.

C드라이브의 프로그램 파일로 들어갔을 때의 폴더주소입니다. 최상위의 경로가 C: 라고 되어있지요? 즉 드라이브의 루트 디렉토리 경로는 C: 입니다. 다만 MSDN의 설명을 참고해보니 경로 끝에 역슬래시(\)가 필요하다고 합니다. 즉 인자로

C:\\가 들어갑니다.

뭔가 이상하지 않습니까? \가 2개나 있습니다. c언어를 잘 배우신 분이라면 특수문자를 표기할 때 \를 붙여야 한다는 것을 아실 겁니다. 

ex) \/, \%, \' , \\ 등

자 이 다음은 설명이 별로 없습니다.

EAX와 ESI를 연산하고 

EAX와 ESI의 값을 비교하여 두 값이 다르면 실패 메세지 박스를 띄우고

성공하면 성공메세지 박스를 띄우는 겁니다.


자 소스코드 설명이 끝났으니 프로그램을 실행시켜 봅시다.

자 이것으로 abex crack me 만들기를 마치....

지 않아야겠죠?

제가 만든 프로그램과 일반 abex crack me가 똑같아 보이십니까? 아니죠?

일반 abex crack me를 실행 시켰을 때 저런 콘솔창 나타나던가요? 아니죠?

저런 콘솔창이 나타나는 이유는 우리가 프로그램을 짤 때 프로젝트 종류를 콘솔 응용 프로그램으로 했기 때문에 저런 콘솔창이 나타는 것입니다.

쓸데 없이 콘솔창이 나오니 멋없어 보이죠? 

그래서 다음 포스트엔 이 콘솔창이 안뜨게 프로그래밍을 해보겠습니다.

즉 콘솔 기반 프로그래밍이 아닌 응용 프로그래밍으로 해보겠습니다.

이상으로 포스팅을 마치겠습니다.

감사합니다.






Posted by englishmath
,

안녕하십니까. 최근 따로 공부를 한다고 블로그 관리가 소홀했던 점 일단 사과드립니다.

이번 카테고리는 직접 코드엔진의 프로그램을 C언어로 만들어보고 그리고 직접 만든 프로그램을 분석하는 식으로 진행할 예정입니다.

왜 이런 방법을 택했느냐고 하니...

솔직히 리버싱이라고 해도 남이 만든 프로그램을 리버싱하면서.. 똑같은 key값 찾기 알고리즘 분석해서 이름이 ~~인 패스워드 찾기 등.. 솔직히 같은 패턴에 너무 질려버렸습니다. 

그래서 이번엔 조금 재밌게 리버싱을 해보고자 직접 프로그램을 제작해보고 그리고 직접 만든 프로그램을 리버싱해보면 어떨까 하고 생각이 들어 이 카테고리를 제작하게 되었습니다.

다음 포스팅은 CodeEngn basic rce level1에 쓰인 프로그램인 abex crack me 1을 한번 c로 만들어보겠습니다.

많이 부족하지만 앞으로 잘 부탁드립니다.

Posted by englishmath
,