안녕하십니까. 이번 포스팅에서는 넷버스의 리소스를 한 번 보여드리겠습니다.

넷버스는 공격자가 쓰는 프로그램과 대상 사용자의 컴퓨터를 감염시킬 때 쓰는 프로그램 이렇게 두 개의 프로그램으로 나뉘므로 두 프로그램의 리소스를 다 보여드리겠습니다. 

편의상 공격자가 쓰는 프로그램을 넷버스, 감염시킬 때 쓰는 프로그램을 패치라고 하겠습니다.

먼저 넷버스의 리소스입니다.

감염된 PC에서 파일을 찾기 위해 만든 대화상자입니다. 현재는 텍스트파일만 찾아내기 위해 버튼을 하나만 만들었습니다.

감염된 PC에 있는 파일들의 경로를 입력하여 해당 파일을 복사할 수 있도록 하기 위해 만든 대화상자입니다. 입력받는 칸은 edit 컨트롤을 이용하여 만들었으며 길이의 제한을 받지 않기 위해 Auto HScroll 옵션을 TRUE로 주었습니다.

넷버스의 아이콘입니다.

생각보다 간단한 리소스이지요? 이번에는 패치의 리소스를 살펴봅시다.

패치는 아이콘외에는 리소스를 추가하지 않았습니다. PC를 감염시키는 용도로 사용하기 때문에 별다른 리소스가 필요하지 않기 때문입니다.

네 이것으로 리소스 포스팅을 마치겠습니다. 다음 포스팅에서는 넷버스에 쓰인 함수들을 살펴보겠습니다.

Posted by englishmath

안녕하십니까? 이번 포스팅에선 제가 만든 넷버스 알고리즘을 한 번 포스팅해보도록 하겠습니다.

넷버스란?

아주 오래전에 만들어진 악성 프로그램 중 하나로 사용자의 PC를 감염시켜 원격조종을 하는 것이 주 기능입니다.

일단 넷버스 알고리즘을 살펴보기 전에 다음과 같은 주의사항을 숙지하시기 바랍니다.

1. 앞으로 소개할 프로그램은 악성기능이 내포되어 있으므로 마음대로 악용을 하지 않습니다.

2. 위 사항을 어겨 불이익을 받을 시 이 블로그는 책임을 지지 않습니다.

3. 악성행위의 목적이 아닌 공부 목적으로 제작한 것이므로 독자분들은 위의 주의사항을 숙지하시고 협조 부탁드립니다.

자 이제 한 번 제가 만든 넷버스 알고리즘을 하나씩 살펴봅시다.

1. 넷버스는 TCP 소켓을 이용하여 통신을 한다.

2. 넷버스는 두개의 프로그램으로 이루어져 있으며 클라이언트(공격자) - 서버(감염자) 통신 방식을 이용한다.

3. 감염자가 자신의 PC에 악성코드를 실행시키면 공격자가 해당 감염자의 IP주소를 입력하여 소켓을 연결한다.

4. 소켓이 연결되면 send, recv api함수를 사용하여 데이터를 빼내온다.

5. 감염자의 PC는 공인IP를 사용하여야 한다. (사설 IP인 경우 공격자와 감염자가 같은 네트워크 상에 속해 있어야 하며 그게 아니라면 감염자에게 사설IP를 제공하는 기기(공유기 등)에 포트포워딩을 해주어야 한다.

6. 필자가 만든 넷버스는 오리지널 넷버스의 겉모습만 비슷하게 만들었을 뿐 제공해주는 기능에는 차이가 있다.

7. 일부 백신은 해당 프로그램을 악성코드로 취급할 수 있다.

이정도 등이 있습니다. 자 그럼 다음 포스팅에선 리소스를 살펴보겠습니다.




Posted by englishmath

안녕하십니까? 이번 포스팅에서는 제가 만든 작업관리자의 코드를 보여드리겠습니다.먼저 코드를 봅시다.

예전에 설명한 코드들은 생략하고 살펴보겠습니다.

- #include <uxtheme.h>

SetWindowTheme함수를 사용하기 위해 선언한 헤더파일입니다.

- #include <CommCtrl.h>

컨트롤들의 디자인을 바꾸기 위해 선언한 헤더파일입니다. 이 헤더파일을 사용할 경우 manifest를 변경해주어야 합니다. 변경하는 법은 메모장 만들기 포스팅에서 언급하였으므로 생략하겠습니다.

- #pragma comment(lib, "comctl32.lib")

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

각각의 라이브러리 파일을 링커에 추가시켜 줍니다.

- LRESULT WINAPI WndProc(HWND hWnd,UINT Message,WPARAM wParam,LPARAM lParam);

  BOOL WINAPI MainDlgProc(HWND hWnd,UINT Message,WPARAM wParam,LPARAM lParam);

  BOOL WINAPI Tab1DlgProc(HWND hWnd,UINT Message,WPARAM wParam,LPARAM lParam);

  BOOL WINAPI EnumWindowsProc(HWND hwnd,LPARAM lParam);

  BOOL WINAPI EnumDesktopProc(LPTSTR lpszDesktop,LPARAM lParam);

각각의 프로시저함수들을 선언하는 부분입니다. 이것은 나중에 자세히 설명드리겠습니다.

- void InsertItemDlglist(HWND *DesktopHWND1, TCHAR **DesktopWND_title1, int count);

  int checkDesktopWND_title(TCHAR *DesktopWND_title1);

  int CMP_DesktopWND_title(void);

  void free_DesktopWND_title(void);

  void free_DesktopWND_title2(void);

  void LVHeaderSort(HWND hWnd,TCHAR *DlgListHeader_Str[],int columnindex);

  void asce_sort(void);

  void desc_sort(void);

  void GetLVHeaderSort(void);

  void MoveWND(HWND hWnd);

앞 포스팅에서 설명드린 함수들을 선언합니다.

- HINSTANCE Global_hInstance;

  HWND Global_MainHWND;

  HWND Global_TabDlgHWND;

WinMain의 hInstance와 각각의 대화상자 핸들을 전역변수에 저장시키기 위해 선언한 전역변수입니다.

- HIMAGELIST Global_ImageList;

이미지리스트를 생성후 전역변수에 저장시키기 위해 선언하였습니다.

- HWND Global_DesktopHWND[100];

  TCHAR *Global_DesktopWND_title[100];

  TCHAR *Global_DesktopWND_title2[100];

데스크탑에서 가져온 윈도우의 핸들과 타이틀을 저장할 배열을 선언합니다.

- int Global_hwnd_count = 0;

  int Global_Dlglist_count = 0;

데스크탑에서 가져온 윈도우의 개수와 리스트 뷰에 추가된 윈도우의 개수를 받을 전역변수를 선언하고 0으로 초기화합니다.

- POINT Global_MINSIZEcoordinate;

작업관리자의 최소 크기를 지정해주기 위해 POINT 구조체 변수를 전역변수로 선언하였습니다. POINT구조체의 멤버는 x와 y이며 x와 y좌표를 저장받습니다.

이제 WinMain 함수를 살펴봅시다.

- Global_hInstance = hInstance;

hInstance값을 전역변수에 저장시킵니다.

- haccel = LoadAccelerators(hInstance,MAKEINTRESOURCE(IDR_ACCELERATOR1));

리소스에서 액셀러레이터를 불러와 변수에 저장시킵니다.

- hWnd = CreateDialog(hInstance,MAKEINTRESOURCE(IDD_DIALOG1),NULL,MainDlgProc);

CreateDialog함수를 사용하여 대화상자를 생성합니다. 이 때 생성하는 대화상자는 IDD_DIALOG1이므로 Tab컨트롤과 static컨트롤이 포함되어 있는 메인대화상자입니다. 이 대화상자는 함수의 마지막 인자인 MainDlgProc함수에서 동작하며 이 때 핸들을 hWnd에 저장시킵니다.

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

if (!TranslateAccelerator(hWnd,haccel,&msg))

EnumDesktops(GetProcessWindowStation(),EnumDesktopProc,0);

TranslateMessage(&msg);

DispatchMessage(&msg);

return 0;

메세지 루프 부분입니다. 다만 여태까지와의 코드에서 다른 점이 있다면 액셀러레이터메세지를 대화상자(hWnd)에서 처리한다는 부분과 EnumDesktops함수가 포함되어있다는 것을 알 수 있습니다. EnumDesktops함수를 살펴봅시다.

지정된 윈도우 스테이션에 포함된 데스크탑을 전부 열거해주는 함수라고 적혀있습니다. 각 인자들을 살펴봅시다.

* HWINSTA hwinsta

윈도우 스테이션의 핸들을 인자로 받습니다.

* DESKTOPENUMPROC lpEnumFunc

윈도우 스테이션에서 가져온 데스크탑을 처리할 콜백 함수의 포인터입니다.

* LPARAM lParam

콜백함수에 추가로 전달될 값을 인자로 받습니다.

즉 정리하면 이 함수는 윈도우 스테이션에서 데스크탑을 전부 가져와 사용자가 처리할 수 있도록 해주는 함수입니다. 일단 이를 자세히 이해할려면 윈도우 스테이션을 알 필요가 있습니다.

윈도우스테이션은 쉽게 얘기하면 윈도우즈에 포함된 하나의 영역이라고 보시면 됩니다. 윈도우즈 운영체제는 여러개의 세션으로 나뉘어지고 세션은 여러개의 윈도우스테이션을 포함하고 있으며 윈도우 스테이션은 여러개의 데스크탑을 포함하고 있는 구조이지요. 

그리고 우리는 작업관리자를 구현하기 위해 데스크탑에서 핸들을 가져와야 하므로 데스크탑을 가져오기 위해 윈도우 스테이션을 가져와야 합니다. 이 때 윈도우 스테이션을 가져오는 함수가 바로 GetProcessWindowStation함수입니다.

GetProcessWindowStation함수는 현재 프로세스와 연관된 윈도우 스테이션을 가져오는 함수이며 따로 사용자가 임의로 프로세스의 윈도우 스테이션을 바꾼 것이 아니라면 기본적으로 WinSta0 윈도우 스테이션을 들고오게 됩니다.

그리고 그렇게 가져온 윈도우 스테이션을 인자값으로 준 EnumDesktopProc함수에서 처리하게 됩니다.

이제 WndProc함수를 설명드릴 차례인데 우리는 윈도우가 아닌 대화상자에서 작업을 하므로 이 WndProc함수는 그냥 기본틀만 잡았습니다. 그러므로 생략하겠습니다.

자 그러면 MainDlgProc함수를 살펴봅시다. 이 함수는 메인 대화상자의 메세지를 처리하는 함수입니다.

- TCITEM Tab;

TCITEM 구조체 변수 Tab을 선언합니다. 이 구조체를 살펴봅시다.

이 구조체는 탭 컨트롤과 관련된 속성을 지정하기 위한 멤버가 들어있습니다. 탭 컨트롤의 정보를 받거나 혹은 추가할 때 사용하는 구조체입니다. 사용한 멤버들만 살펴봅시다.

* UINT mask

어떠한 멤버를 유효멤버로 정할지를 값으로 받는 멤버입니다. 이 값에 따라 유효멤버가 바뀌며 특정 멤버는 유효멤버가 아니면 어떤 값이 들어가든 무효가 되버립니다.

* LPTSTR pszText

탭 컨트롤에 지정할 문자열을 값으로 받습니다.

- TCHAR *Tab_String[] = {L"응용 프로그램",L"프로세스",L"서비스",L"성능",L"네트워킹",L"사용자"};

각각의 탭 컨트롤에 들어갈 문자열을 선언합니다.

- int i;

포커싱된 탭 컨트롤의 인덱스 값을 받기 위해 선언하였습니다.

- LPMINMAXINFO MINMAX;

작업관리자의 최소크기를 변경하기 위해 LPMINMAXINFO 구조체를 선언합니다. 이 구조체를 한 번 살펴봅시다.

윈도우의 최대 크기 및 최소크기를 저장한다고 되어 있습니다. 사용한 멤버만 살펴봅시다.

*POINT ptMinTrackSize

윈도우의 최소크기를 값으로 받는 멤버변수입니다. POINT 구조체의 멤버변수인 x가 최소넓이가 되며 y는 최소 높이가 됩니다.

- WINDOWINFO window;

윈도우의 정보를 담는 WINDOWINFO구조체 변수를 선언합니다.

- switch(Message)

case WM_INITDIALOG:

대화상자가 생성될 때 발생하는 메세지를 받았을 때 아래 코드를 수행합니다.

- Global_MainHWND = hWnd;

핸들을 전역변수에 저장시킵니다.

- SetWindowPos(hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE);

SetWindowPos함수를 사용하였습니다. 이 함수를 살펴봅시다.

윈도우의 크기와 위치, z축 순서를 변경시켜주는 함수입니다. z축 순서를 변경시킨다는 점에서 MoveWindow함수와 차이가 나는군요. 인자들을 살펴봅시다.

* HWND hWnd

변경될 윈도우의 핸들입니다.

* HWND hWndInsertAfter

윈도우의 Z축 위치를 설정합니다. 여기서는 HWND_TOPMOST값을 주어 작업관리지가 어떠한 윈도우보다도 높은 곳에 위치하도록 합니다.

* int   X

윈도우의 x좌표입니다.

* int   Y

윈도우의 y좌표입니다.

* int   cx

윈도우의 너비입니다.

* int   cy

윈도우의 높이입니다.

* UINT uFlags

윈도우에 적용할 추가옵션입니다. 여기서는 SWP_NOMOVE와 SWP_NOSIZE 값을 주어 이 함수가 실행될 때 작업관리자의 위치와 크기가 변경되지 않도록 하였습니다.

- window.cbSize = sizeof(window);

window의 cbSize멤버값에 구조체의 크기를 넣어줍니다.

- GetWindowInfo(hWnd,&window);

GetWindowInfo함수를 사용하여 현재 메인 대화상자의 정보를 window구조체에 저장시킵니다.

- Global_MINSIZEcoordinate.x = window.rcWindow.right - window.rcWindow.left;

POINT 구조체 전역변수의 멤버인 x에 메인 대화상자의 right좌표값에서 left좌표값을 뺀 값을 저장시킵니다. 이렇게 하면 메인 대화상자가 생성될 때의 너비값이 x멤버에 저장됩니다. 즉 이 값이 메인 대화상자의 최소 너비값이 됩니다.

- Global_MINSIZEcoordinate.y = window.rcWindow.bottom - window.rcWindow.top;

POINT 구조체 전역변수의 멤버인 y에 메인 대화상자의 bottom좌표값에서 top좌표값을 뺀 값을 저장시킵니다. 이렇게 하면 메인 대화상자가 생성될 때의 높이값이 ㅛ멤버에 저장됩니다. 즉 이 값이 메인 대화상자의 최소 높이값이 됩니다.

- SendMessage(hWnd,WM_SETICON,ICON_BIG,(LPARAM)LoadIcon(Global_hInstance,MAKEINTRESOURCE(IDI_ICON1)));

메인 대화상자에 WM_SETICON메세지를 보내 아이콘을 작업관리자 아이콘으로 변경합니다.

- SetMenu(hWnd,LoadMenu(Global_hInstance,MAKEINTRESOURCE(FileMenu)));

리소스에 작성된 메뉴를 메인 대화상자에 추가시킵니다. 

- SetWindowText(hWnd,L"Windows 작업 관리자");

메인 대화상자의 타이틀을 Windows 작업 관리자로 변경합니다.

- for(i=0;i<6;i++)

Tab.mask = TCIF_TEXT;

Tab.pszText = Tab_String[i];

      SendDlgItemMessage(hWnd,Tab1,TCM_INSERTITEM,i,(LPARAM)&Tab);

for문과 SendDlgItemMessage함수를 사용하여 총 6개의 탭을 탭컨트롤에 추가시킵니다. 문자열은 앞에서 선언한 Tab_String값입니다.

- SendDlgItemMessage(hWnd,Tab1,TCM_SETCURSEL,0,0);

탭컨트롤에 TCM_SETCURSEL메세지를 보내 wParam(0) 탭을 선택하도록 합니다. 즉 이말은 기본 포커싱을 0번 째 탭(응용프로그램)으로 지정하겠다는 뜻입니다.

-    CreateDialog(Global_hInstance,MAKEINTRESOURCE(Tab1_DIALOG),hWnd,Tab1DlgProc);

메인 대화상자의 설정이 모두 끝났으면 리스트뷰가 들어있는 Tab1_DIALOG대화상자를 생성합니다. 프로시저함수는 Tab1DlgProc입니다.

- case WM_PAINT:

무효화 영역이 생겨서 다시 그려야 할 경우 아래의 코드를 수행합니다.

- i = SendDlgItemMessage(hWnd,Tab1,TCM_GETCURFOCUS,0,0);

현재 포커스된 탭의 인덱스를 가져와 i에 저장시킵니다.

- switch(i)

case 0:

  ShowWindow(Global_TabDlgHWND,SW_HIDE);

ShowWindow(Global_TabDlgHWND,SW_SHOW);

break;

default:

break;

탭의 인덱스가 0(응용프로그램)이면 메인 대화상자를 숨기고 다시 보여줍니다.

만약 다른 인덱스라면 대화상자를 보여주지 않습니다.

- case WM_CLOSE:

PostQuitMessage(0);

메인 대화상자의 닫기버튼을 눌렀을 경우 PostQuitMessage함수를 호출하여 프로그램을 종료시킵니다.

- case WM_SIZE:

대화상자의 크기 관련 메세지를 받을 경우 아래의 코드를 수행합니다.

- if(IsIconic(hWnd))

SetWindowPos(hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE);

IsIconic함수의 결과값이 참일 경우 SetWindowPos함수를 사용하여 메인 대화상자의 Z위치를 HWND_TOPMOST로 줍니다. IsIconic함수는 지정한 핸들이 최소화 된 핸들일 경우 참을 반환하는 함수이며 보통 최소화된 창을 눌러 크기를 복구 시킬 때에는 Z축 위치가 변경되므로 Z축의 위치를 원상태로 복구하기 위해 작성한 코드입니다.

- MoveWND(hWnd);

그리고 위의 if문과는 관계 없이 MoveWND함수를 호출하여 메인 대화상자의 크기에 맞게 컨트롤들의 크기와 위치를 변경시킵니다.

- case WM_GETMINMAXINFO:

메인 대화상자의 크기나 위치가 변경되었을 경우 발생하는 메세지입니다. 이 때 lParam은 MINMAXINFO 구조체이며 이 구조체의 값을 변경함으로써 메인 대화상자의 최대크기나 최소크기를 변경할 수 있습니다.

- MINMAX = (LPMINMAXINFO)lParam;

MINMAXINFO구조체 값인 lParam을 MINMAX에 저장시킵니다.

- MINMAX->ptMinTrackSize = Global_MINSIZEcoordinate;

MINMAX구조체의 ptMinTrackSize멤버에 아까 메인대화상자가 생성될 때 지정해준 최소크기 값이 들어있는 Global_MINSIZEcoordinate구조체 값을 넣어줍니다. 이렇게 하면 사용자가 크기 변경을 요청해도 Global_MINSIZEcoordinate의 크기보다 작게는 변경되지 않습니다.

- case WM_NOTIFY:

메인 대화상자와 관련된 이벤트가 발생했을 경우 아래의 코드를 수행합니다. 이 때 lParam은 LPNMHDR구조체 값입니다. 이 구조체를 한 번 살펴봅시다.

이벤트에 대한 정보를 담는 구조체라고 되어 있습니다. 이중 사용한 멤버만 살펴봅시다.

* UINT code

어떠한 이벤트가 발생했는지를 알려주는 코드입니다.

- switch(((LPNMHDR)lParam)->code)

NMHDR 멤버의 code값을 switch함수의 인자로 주어 각 이벤트마다 다른 처리를 하도록 하였습니다.

- case TCN_SELCHANGE:

code값이 TCN_SELCHANGE일때의 처리입니다. 즉 탭 컨트롤이 변경되었다는 것을 알알리는 코드이지요. 탭 컨트롤이 변경되었을 경우 아래의 코드를 수행합니다.

- i = SendDlgItemMessage(hWnd,Tab1,TCM_GETCURFOCUS,0,0);

현재 선택된 탭 컨트롤의 인덱스를 가져옵니다.

- switch(i)

case 0:

ShowWindow(Global_TabDlgHWND,SW_SHOW);

break;

default:

ShowWindow(Global_TabDlgHWND,SW_HIDE);

break;

0번째 탭(응용 플로그램)을 클릭했을 경우 메인 대화상자를 보여주고 그 외의 탭이라면 대화상자를 감춥니다.

- return FALSE;

WndProc함수의 DefWindowProc함수와 동일한 기능을 합니다.

이제 리스트 뷰가 포함되어 있는 대화상자 프로시저를 살펴봅시다.

- LVCOLUMN DlgListHeader = {0};

LVCOLUMN구조체 변수를 선언하고 0으로 초기화합니다. LVCOLUMN구조체를 살펴봅시다. 

리스트뷰의 열에 관한 정보를 담고 있는 구조체라고 되어 있습니다. 이 구조체는 리스트뷰 열을 추가할 때 사용합니다. 사용한 멤버만 살펴봅시다.

* UINT mask

어떠한 멤버를 유효멤버로 정할지를 값으로 받는 멤버입니다. 이 값에 따라 유효멤버가 바뀌며 특정 멤버는 유효멤버가 아니면 어떤 값이 들어가든 무효가 되버립니다.

* int cx

열의 너비값을 받는 멤버입니다.

* LPTSTR pszText

열의 문자열을 받는 멤버입니다.

- TCHAR *DlgListHeader_Str[] = {L"작업",L"상태"};

열의 문자열들을 저장할 변수를 선언한 후 문자열을 집어넣습니다.

- int i;

for문에 사용할 변수를 선언하였습니다.

- int size = 270;

열의 너비값을 저장하는 변수 size를 선언하였습니다.

- HWND LVHeader;

  HDITEM HeaderItem;

리스트뷰 헤더의 핸들을 저장할 변수와 헤더의 정보를 저장할 HDITEM 구조체 변수를 선언합니다.

- int itemindex;

리스트뷰의 항목들의 인덱스를 저장할 변수를 선언하였습니다.

- switch(Message)

case WM_INITDIALOG:

리스트뷰를 담고 있는 대화상자가 생성될 때 아래코드를 수행합니다.

- Global_ImageList = ImageList_Create(16,16,ILC_MASK | ILC_COLOR32,10,0);

ImageList_Create함수를 사용하여 이미지리스트를 생성후 그 핸들을 Global_ImageList에 저장합니다. ImageList_Create함수를 살펴봅시다.

함수명 그대로 이미지리스트를 생성하는 함수입니다. 인자들을 살펴봅시다.

* int cx

이미지리스트에 저장될 이미지의 너비입니다. 우리는 작은 아이콘을 넣어야 하므로 이미지의 너비를 16으로 주었습니다.

* int cy

이미지리스트에 저장될 이미지의 높이입니다. 우리는 작은 아이콘을 넣어야 하므로 이미지의 높이를 16으로 주었습니다.

* UINT flags

이미지 리스트의 추가옵션을 받는 인자입니다. 어기서는 ILC_MASK값과 ILC_COLOR32값을 주어 32bit DIB섹션을 사용하도록 하였습니다.

* int cInitial

이미지리스트에 저장될 이미지의 개수입니다. 여기서는 10개로 주었습니다.

* int cGrow

이 인자는 무슨 값을 받는지를 몰라 그냥 NULL값을 주었습니다.

즉 정리하면 우리는 16*16 크기의 이미지를 32bit DIB섹션을 사용하여 10개 저장하는 이미지리스트를 생성하여 그 핸들을 전역변수에 저장시켰습니다.

- Global_TabDlgHWND = hWnd;

리스트뷰가 들어있는 대화상자의 핸들을 전역변수에 저장힙니다.

- for(i=0;i<2;i++)

리스트뷰의 헤더를 추가하기 위해 for문을 돌립니다.

- DlgListHeader.mask = LVCF_TEXT | LVCF_WIDTH;

mask멤버에 LVCF_TEXT | LVCF_WIDTH 값을 넣습니다. LVCF_TEXT는 pszText멤버를, LVCF_WIDTH는 cx멤버를 유효화시킵니다.

- DlgListHeader.cx = size;

cx멤버에 헤더의 너비값이 들어있는 size값을 넣습니다.

- DlgListHeader.pszText = DlgListHeader_Str[i];

pszText에 DlgListHeader_Str배열의 i번째 문자열을 대입합니다. 이렇게 하면 차례대로 작업, 상태가 들어가게 됩니다.

- SendDlgItemMessage(hWnd,Tab1_DLG_List,LVM_INSERTCOLUMNW,i,(LPARAM)&DlgListHeader);

리스트뷰에 LVM_INSERTCOLUMNW메세지를 보내 리스트뷰의 헤더를 추가합니다. 이때 헤더의 인덱스는 wParam(i)입니다.

- size = 60;

첫번째 헤더(작업)추가가 끝났으면 다음 헤더(상태)의 너비를 size값에 넣습니다.

for문이 끝나면 아래의 코드를 수행합니다.

- LVHeader = (HWND)SendDlgItemMessage(hWnd,Tab1_DLG_List,LVM_GETHEADER,0,0); 

LVM_GETHEADER메세지를 보내 방금 추가한 헤더의 핸들을 얻어옵니다.

- HeaderItem.mask = HDI_TEXT | HDI_FORMAT;

  HeaderItem.fmt = HDF_SORTUP | HDF_STRING;

  HeaderItem.pszText = DlgListHeader_Str[0];

  SendMessage(LVHeader,HDM_SETITEM,0,(LPARAM)&HeaderItem);

HDITEM구조체의 멤버값에 값을 넣고 HDM_SETITEM메세지를 보내 0번째 헤더를 수정합니다. 이 코드를 수행하면 작업관리자를 처음으로 실행할 때 기본적으로 작업 헤더가 오름차순을 뜻하는 삼각형을 갖게 됩니다.

-    SendDlgItemMessage(hWnd,Tab1_DLG_List,LVM_SETEXTENDEDLISTVIEWSTYLE,0,LVS_EX_FULLROWSELECT);

리스트뷰에 LVM_SETEXTENDEDLISTVIEWSTYLE메세지를 보내 리스트뷰의 확장스타일을 설정합니다. 이때 스타일은 lParam값인 LVS_EX_FULLROWSELECT스타일이며 이 값을 주면 리스트뷰에서 선택한 아이템과 아이템의 하위 항목이 동시에 표시됩니다.만약 이 스타일을 주지 않는다면 아이템을 선택했을 때 아이템의 하위항목은 표시되지 않습니다.

- SetWindowTheme(GetDlgItem(hWnd,Tab1_DLG_List),L"Explorer",NULL);

이 함수는 지정한 핸들의 시각적 스타일을 지정한 응용프로그램의 시각적 스타일로 바꿔주는 함수입니다. 즉 우리가 만든 리스트뷰의 시각적 스타일을 응용프로그램인 Explorer(탐색기)의 시각적 스타일로 바꿔준다고 보시면 됩니다. 세번째 인자는 무슨 뜻인지 몰라 NULL값을 주었습니다.

- SetFocus(GetDlgItem(hWnd,Tab1_DLG_List));

키보드 포커스를 리스트뷰에 맞춥니다. 이 코드가 수행되면 작업관리자가 실행될 때 리스트뷰에서 키보드 값을 받게 됩니다. 만약 이코드가 없다면 자동으로 키보드 포커싱이 탭 컨트롤로 지정됩니다.

- EnableWindow(GetDlgItem(hWnd,TaskExit),FALSE);

  EnableWindow(GetDlgItem(hWnd,change),FALSE);

처음 작업관리자가 실행될 때에는 아무런 아이템도 선택되지 않으므로 작업 끝내기 버튼과 전환 버튼을 비활성화시킵니다.

- ShowWindow(hWnd,SW_SHOW);

모든 설정이 다 끝났으면 SW_SHOW옵션을 주어 윈도우를 보이게 만듭니다.

- case WM_COMMAND:

사용자가 메뉴나 컨트롤을 눌렀을 경우 발생하는 메세지이며 아래의 코드를 수행합니다.

- switch(LOWORD(wParam))

메뉴나 컨트롤의 ID값이 들어있는 하위 wParam값에 따라 처리가 달라집니다.

- case TaskExit:

itemindex = SendDlgItemMessage(hWnd,Tab1_DLG_List,LVM_GETNEXTITEM,-1,LVNI_SELECTED);

SendMessage(Global_DesktopHWND[itemindex],WM_CLOSE,0,0);

작업 끝내기 버튼을 눌렀을 경우 리스트뷰에 LVM_GETNEXTITEM메세지를 보내 리스트뷰의 항목을 전부 검사합니다. 이 때 LVNI_SELECTED옵션이 적용된 항목을 찾아 그 항목의 인덱스를 itemindex에 저장합니다. 여기서 LVNI_SELECTED옵션은 선택된 항목을 뜻합니다.

항목의 인덱스를 받았다면 그 인덱스에 해당하는 핸들에 WM_CLOSE메세지를 보내 닫기를 요청합니다.

- case change:

ShowWindow(Global_MainHWND,SW_MINIMIZE);

  itemindex = SendDlgItemMessage(hWnd,Tab1_DLG_List,LVM_GETNEXTITEM,-1,LVNI_SELECTED);

SwitchToThisWindow(Global_DesktopHWND[itemindex],TRUE);

전환 버튼을 눌렀을 경우에는 먼저 작업관리자에 SW_MINIMIZE옵션을 적용하여 최소화 시킨후 해당하는 리스트뷰 항목의 인덱스를 구해 SwitchToThisWindow함수를 사용하여 포커스를 인덱스에 해당하는 핸들로 맞춘 후 포그라운드로 가져옵니다.

- case WM_NOTIFY:

switch(((LPNMHDR)lParam)->code)

이벤트 통지에 따라 아래의 코드를 수행합니다.

- case LVN_COLUMNCLICK:

switch(((LPNMLISTVIEW)lParam)->iSubItem)

리스트뷰의 칼럼을 클릭했다는 메세지를 받으면 lParam은 NMLISTVIEW구조체의 주소값을 받게 됩니다. 이 때 NMLISTVIEW구조체의 멤버중 하나인 iSubItem을 확인하여 어떤 칼럼을 클릭했는지에 따라 처리를 달리합니다.

- case 0:

LVHeaderSort(hWnd,DlgListHeader_Str,0);

break;

  case 1:

   LVHeaderSort(hWnd,DlgListHeader_Str,1);

break;

각각 해당하는 칼럼을 눌렀을 경우 LVHeaderSort함수를 호출하여 상황에 따라 누른 칼럼을 오름차순 혹은 내림차순으로 바꾸거나 아니면 아무것도 나타내지 않는 빈 헤더로 바꿉니다. 이때 함수의 세번째 인자값은 누른 칼럼의 인덱스 값입니다. 

- case LVN_ITEMCHANGED:

리스트뷰의 항목변경이 일어난 경우 발생하는 메세지입니다. 이때 아래의 코드를 수행합니다.

- switch(((LPNMLISTVIEW)lParam)->uNewState)

lParam의 멤버인 uNewState값에 따라 처리를 달리합니다. uNewState는 리스트뷰의 항목에 대한 상태를 나타냅니다. 

- case 0:

EnableWindow(GetDlgItem(hWnd,TaskExit),FALSE);

EnableWindow(GetDlgItem(hWnd,change),FALSE);

uNewState값이 0이라는 것은 어떠한 항목도 선택되지 않았음을 나타냅니다. 이럴 때에는 작업 끝내기 버튼과 전환 버튼을 비활성화 시킵니다.

- case 3:

EnableWindow(GetDlgItem(hWnd,TaskExit),TRUE);

EnableWindow(GetDlgItem(hWnd,change),TRUE);

uNewState값이 3이라는 것은 어떠한 항목이 선택되었음을 나타냅니다. 이럴 때에는 작업 끝내기 버튼과 전환 버튼을 활성화 시킵니다.

자 다음으로 EnumDesktopProc함수를 살펴봅시다. 이 함수는 앞의 WinMain함수에서 등장한 EnumDesktops함수의 프로시저함수입니다.

- HDESK desktop;

데스크탑의 핸들을 받는 변수를 선언합니다.

- desktop = OpenDesktop(lpszDesktop,0,NULL,DESKTOP_READOBJECTS);

OpenDesktop함수를 사용하여 지정한 데스크탑을 엽니다. OpenDesktop함수를 살펴봅시다.

말 그대로 지정한 데스크탑을 여는 함수입니다. 인자들을 살펴봅시다.

* LPTSTR lpszDesktop

데스크탑 명을 인자로 받습니다. 여기서는 EnumDesktopProc함수가 반환하는 데스크탑의 명인 lpszDesktop을 인자로 주었습니다.

* DWORD dwFlags

0과 DF_ALLOWOTHERACCOUNTHOOK 둘중 하나의 값이 될 수 있다고 합니다. 저는 이 인자에 관해 잘 모르므로 그냥 0으로 주었습니다.

* BOOL fInherit

상속 관련 인자입니다. 역시 잘 모르므로 NULL값을 주었습니다.

* ACCESS_MASK dwDesiredAccess

권한을 설정하는 부분입니다. 우리는 데스크탑에서 동작하는 윈도우를 읽어야 하므로 그에 맞는 권한인 DESKTOP_READOBJECTS권한을 주었습니다. 

정리하면 EnumDesktopProc함수에서 받는 데스크탑을 여는 함수라고 보시면 됩니다. EnumDesktopProc는 여러개의 데스크탑을 반환하지만 개발자가 따로 코드를 작성하지 않는 이상 기본적으로 Default 데스크탑을 반환합니다. Default데스크탑은 사용자의 데스크탑이라고 보시면 됩니다.

- Global_hwnd_count = 0;

핸들의 개수를 0으로 초기화합니다.

- EnumDesktopWindows(desktop,EnumWindowsProc,NULL);

EnumDesktopWindows함수를 호출합니다. 이 EnumDesktopWindows함수는 인자로 받은 데스크탑과 관련된 모든 윈도우를 열거하는 함수입니다. 이 때 프로시저함수는 EnumWindowsProc함수입니다.

- GetLVHeaderSort();

리스트뷰 헤더의 정렬상태를 읽어와 그에 맞게 배열을 정렬시키는 함수GetLVHeaderSort를 호출합니다.

- if(Global_Dlglist_count == 0)

InsertItemDlglist(Global_DesktopHWND,Global_DesktopWND_title,Global_hwnd_count);

리스트뷰에 추가된 항목의 개수가 0인 경우 InsertItemDlglist함수를 호출하여 리스트뷰에 항목을 추가합니다.

- else if(Global_Dlglist_count != Global_hwnd_count)

free_DesktopWND_title2();

InsertItemDlglist(Global_DesktopHWND,Global_DesktopWND_title,Global_hwnd_count);

리스트뷰에 추가된 항목의 개수와 데스크탑에서 가져온 핸들의 개수가 다른 경우 DesktopWND_title2배열을 해제하고 InsertItemDlglist함수를 호출합니다. 

추가로 if부분의 코드는 처음 리스트뷰에 항목을 추가하는 부분이기 때문에 DesktopWND_title2를 해제해주지 않습니다. 동적할당이 되어 있지 않으니까요.

- else if(Global_Dlglist_count == Global_hwnd_count)

if(CMP_DesktopWND_title())

InsertItemDlglist(Global_DesktopHWND,Global_DesktopWND_title,Global_hwnd_count);

리스트뷰에 추가된 항목의 개수와 데스크탑에서 가져온 핸들의 개수가 같은 경우 CMP_DesktopWND_title함수를 호출하여 리스트뷰에 추가된 윈도우들의 타이틀과 데스크탑에서 가져온 윈도우들의 타이틀을 비교합니다. 만약 하나의 윈도우라도 타이틀이 서로 다르다면 InsertItemDlglist함수를 호출하여 리스트뷰를 다시 작성합니다.

- CloseDesktop(desktop);

  free_DesktopWND_title();

모든 처리가 다 끝났으면 열린 데스크탑을 닫고 DesktopWND_title 배열을 해제시켜줍니다.

드디어 마지막이군요. EnumDesktopWindows함수의 프로시저 함수인 EnumWindowsProc을 살펴봅시다. 이 함수는 데스크탑과 관련된 모든 윈도우를 전부 열거해주는 기능을 하며 TRUE를 반환할 경우 다음 윈도우를 열거하고, FALSE를 반환하면 다음 윈도우를 열거하지 않고 바로 종료합니다. 다만 윈도우를 전부 열거하였다면 자동으로 FALSE를 반환합니다.

- DWORD PID,PID2;

프로세스의 ID를 저장할 변수 2개를 선언합니다.

- if(GetWindowTextLength(hWnd) == 0)

return TRUE;

윈도우의 타이틀이 없을 경우 TRUE를 반환하여 다음 윈도우로 넘어가도록 합니다.

- if(GetParent(hWnd) != NULL )

return TRUE;

윈도우가 부모윈도우를 가지고 있는 경우 즉 자식윈도우인 경우 TRUE를 반환하여 다음 윈도우로 넘어가도록 합니다.

- if(IsWindowVisible(hWnd) == FALSE)

return TRUE;

윈도우가 WS_VISIBLE 스타일을 갖지 않는 경우 TRUE를 반환하여 다음 윈도우로 넘어가도록 합니다.

- GetWindowThreadProcessId(Global_MainHWND,&PID);

작업관리자(메인 대화상자)핸들로부터 프로세스 ID를 가져와 PID에 저장시킵니다.

- GetWindowThreadProcessId(hWnd,&PID2);

EnumWindowsProc에서 열거한 윈도우의 프로세스 ID를 가져와 PID2에 저장시킵니다.

- if(PID == PID2)

return TRUE;

두 프로세스 ID값이 같으면 TRUE를 반환하여 다음 윈도우로 넘어가도록 합니다. 이는 작업관리자의 리스트뷰에 자기 자신의 윈도우를 추가하지 않도록 하기 위함입니다.

- Global_DesktopWND_title[Global_hwnd_count] = (TCHAR *)malloc(sizeof(TCHAR)*(GetWindowTextLength(hWnd)+1));

Global_DesktopWND_title배열을 현재 윈도우 타이틀의 크기만큼 동적할당합니다.

- memset(Global_DesktopWND_title[Global_hwnd_count],0,sizeof(TCHAR)*(GetWindowTextLength(hWnd)+1));

동적할당된 배열을 초기화시킵니다.

- GetWindowText(hWnd, Global_DesktopWND_title[Global_hwnd_count], 256);

윈도우의 타이틀을 256자만큼 갖고와 Global_DesktopWND_title에 저장시킵니다.

- Global_DesktopHWND[Global_hwnd_count] = hWnd;

윈도우의 핸들을 전역변수 배열에 저장시킵니다.

- Global_hwnd_count++;

저장이 완료되면 핸들의 카운트를 하나 증가시킵니다.

- if(checkDesktopWND_title(Global_DesktopWND_title[Global_hwnd_count-1]))

checkDesktopWND_title함수를 사용하여 Global_DesktopWND_title에 저장된 문자열이 리스트뷰에 추가되도 되는지 검사합니다. 만약 추가되지 않아야 하는 문자열이라면 참을 반환하고 아래의 코드를 수행합니다.

- free(Global_DesktopWND_title[Global_hwnd_count-1]);

부적절한 문자열이 들어간 인덱스 배열을 해제합니다.

- Global_hwnd_count--;

윈도우 핸들의 카운트도 하나 감소시킵니다.

- return TRUE;

다음 윈도우를 검사하기 위해 TRUE를 반환합니다.

- return TRUE;

하나의 윈도우가 정상적으로 처리가 되었다면 다음 윈도우를 열거하기 위해 TRUE를 반환합니다.

여기까지가 제가 작성한 작업관리자의 코드입니다. 물론 구현하지 않은 부분이 굉장히 많이 있고 자잘한 오류가 있을 수도 있습니다. 이에 대한 수정코드는 나중에 블로그에 포스팅하겠습니다. 결과를 한 번 살펴봅시다.

왼쪽이 제가 작성한 작업관리자, 오른쪽이 오리지널 작업관리자입니다. 자기 자신의 윈도우는 가져오지 않고 서로 작업관리자 윈도우를 가져옵니다.

위치를 Z축의 가장 높은 곳으로 설정하여 다른 윈도우와는 다르게 상태표시줄보다도 높은 곳에 위치하는 것을 볼 수 있습니다.

마지막으로 정렬도 제대로 되는 것을 확인할 수 있습니다.

네 이것으로 작업관리자 포스팅을 마치겠습니다. 따로 궁금하신 점이 있으시다면 댓글이나 방명록에 올려주십시요. 성실히 답변해드리겠습니다.

Posted by englishmath