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

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

Tiếp nối phần 1 và phần 2-1 của loạt blog về phát triển phần mềm độc hại, trong blog này sẽ tiếp tục giới thiệu tới các bạn về các quy trình tiếp theo để phát triển một phần mềm độc hại.

Các bạn vẫn có thể xem lại phần 1 và phần 2-1 tại đây:

>> 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

Trong các phần trước của loạt bài này, Bizfly Cloud đã giới thiệu khái niệm viết một phần mềm độc hại hoàn toàn không thể phát hiện được: http://niiconsulting.com/checkmate/2018/02/malware-development-welcome-dark-side-part-1/  và viết một socket phía client bằng cách sử dụng Windows API http://niiconsulting.com/checkmate/2018/02/malware-development-welcome-dark-side-part-2-1/ . Trong blog này, chúng tôi sẽ phân tách phản ứng nhận được từ máy chủ netcat và lời nhắc phản hồi xem liệu lệnh đó có được phân tách hay không. Và cuối cùng, chúng tôi sẽ cố gắng hiểu các cờ biên dịch và các tùy chọn mà chúng tôi đang sử dụng để biên dịch nhị phân.

Vì chúng ta đã có TCP Socket client sẵn sàng, điều tiếp theo chúng ta cần làm là tạo một bộ đệm sẽ lưu trữ lệnh mà chúng ta sẽ nhận được từ máy chủ netcat / python. Hầu hết mọi người sẽ xem xét sử dụng tiêu đề <strings.h> để tạo, chủ yếu là vì nó an toàn và tự động thêm một kí tự sang dòng mới để ngăn chặn bộ đệm khi sử dụng strcpy. Điều này giúp chúng tôi tạo ra một nhị phân tự do bộ đệm tràn bộ nhớ. 

Nhưng vấn đề vẫn còn thắc mắc ở đây là tiêu đề <string> chiếm bao nhiêu không gian. Sử dụng các string header sẽ tự động đẩy kích thước của tệp nhị phân lên 800 KB, đó chính là vấn đề từ góc độ phần mềm độc hại. Đây là lý do tại sao chúng ta sẽ phải tiếp tục với char từ C.

Char có những nhược điểm riêng. Nó luôn luôn phải được bắt đầu với một chiều dài cụ thể được lưu trữ trong ngăn xếp. Nếu chúng ta muốn một kích thước động, một char có độ dài mà chúng ta không biết, thì chúng ta sẽ phải lưu trữ char trên heap thay vì stack. Ngoài ra, chúng ta cần sử dụng strcpy memcpy thật cẩn thận để không gây ra việc bị tràn bộ đệm. Lý do chính là strcpy dễ bị tổn thương đến công suất vô hạn

Mỗi khi chúng ta xác định giá trị của char mà bị rỗng, chúng ta cần đặt socket ở chế độ chặn sẽ duy trì được việc lắng nghe một lệnh/bộ đệm sắp hiển thị. Một khi nhận được lệnh, chúng tôi sẽ in lệnh đã nhận cùng với kích thước của bộ đệm nhận được trên màn hình. Để tính toán kích thước của bộ đệm, chúng ta cũng cần xác định một số nguyên.

Code sẽ trông như thế này:

else {

       std::cout << "[ ] Connected to client. waiting for incoming command..." << std::endl;

       char CommandReceived[DEFAULT_BUFLEN] = "";

       while (true)

       {

           int Result = recv(tcpsock, CommandReceived, DEFAULT_BUFLEN, 0);

           std::cout << "Command received: " << CommandReceived;

           std::cout << "Length of Command received: " << Result << std::endl;

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

       }

   }

Đặt mã ở trên trong dấu ngoặc vuông để có thể chèn code để in trạng thái kết nối. Ở đây, Result là biến số nguyên sẽ lưu trữ giá trị của bộ đệm nhận được, và CommandReceived là bộ đệm sẽ lưu trữ lệnh. Kích thước của CommandReceived được đặt ở mức tối đa là 1024 mà chúng tôi đã xác định trước đó bằng cách sử dụng #define vì đó cũng là bộ đệm tối đa mà socket của chúng tôi có thể tích lũy. 

Ngoài ra, recvlà một cửa sổ Windows API được chèn vào trong một vòng lặp while (true) để đặt socket vào chế độ chặn và nghe để lấy lệnh và nó sẽ vẫn tiếp tục cho đến khi nào nhận được lệnh thoát.

Dòng cuối cùng với code memset được sử dụng để thiết lập lại giá trị của charCommandReceived với kích thước thực và bộ đệm trống. Lý do chúng tôi làm điều này là vì nếu chúng tôi nhận được một lệnh với kích thước bộ đệm nhỏ sau khi nhận được một lệnh với kích thước bộ đệm lớn hơn, thì dung lượng bộ đệm còn lại trong kích thước bộ đệm nhỏ hơn sẽ bị đầy với dữ liệu sai như hình dưới đây:  

Bộ đệm chính: [powershell]

p

o

w

e

r

s

h

e

l

l

0

1

2

3

4

5

6

7

8

9


Bộ đệm phụ: [whoami]

w

h

o

a

m

i

h

e

l

l

0

1

2

3

4

5

6

7

8

9

Như bạn có thể thấy, nếu việc đặt lại bộ nhớ không được thực hiện, lệnh whoami sẽ trở thành whoamihell, bởi vì chỉ có 6 ký tự đầu tiên bị ghi đè trong khi bộ đệm không bị thay đổi kích thước hoặc bị xóa.

Sử dụng <strings.h> sẽ dễ dàng hơn nhiều ở đây vì nó tự động tối ưu hóa, nhưng phải trả phí tăng kích thước của nhị phân.

Bây giờ, chúng ta viết một bộ mã khác để so sánh lệnh nhận được với một chuỗi ký tự sẽ định tuyến đến một hàm. Ví dụ, tôi sẽ lấy ba lệnh: whoami, pwd, exit. Mỗi khi chúng ta nhận được một lệnh, chúng ta sẽ kiểm tra xem nó có khớp với một chuỗi hay không. Nếu có, sau đó chúng ta in lệnh nhận được trên màn hình và thực thi một hàm. Chúng tôi sẽ viết về hàm này trong phần tiếp theo.

if ((strcmp(CommandReceived, "whoami") == 0)) {

   std::cout << "Command parsed: whoami" << std::endl;

   //Execute a whoami() function

}

else if ((strcmp(CommandReceived, "pwd") == 0)) {

   std::cout << "Command parsed: pwd" << std::endl;

   //Execute a pwd() function

}

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

   std::cout << "Command parsed: exit";

   std::cout << "Closing connection" << std::endl;

   //Exit gracefully

}

else {

   std::cout << "Command not parsed!" << std::endl;

}

Mã này sẽ nằm phía trên phần memset của code.

Cuối cùng, chúng tôi sẽ tiếp tục và biên dịch tệp nhị phân với một số cờ và các tùy chọn mingw như được liệt kê dưới đây:

For Linux:

$ i686-w64-mingw32-g -std=c 11 maldev.cpp -o maldev.exe -s -lws2_32 -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc -static-libgcc

For Windows:

> g -std=c 11 maldev.cpp -o maldev.exe -s -lws2_32 -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc -static-libgcc

Khi chúng tôi chạy nhị phân, kết quả sẽ như hình dưới đây:

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

Ở bên trái, chúng tôi có lắng nghe nhị phân netcat cho các kết nối đến và ở phía bên phải, chúng tôi thực thi phần mềm độc hại của mình. Tuy nhiên, đã xảy ra một số sự cố ở đây. Không có lệnh nào mà chúng tôi gửi được phân tách.

Một vấn đề khác ở đây là, nếu chúng ta thấy lệnh whoami, chiều dài các chữ cái của whoami sẽ là 6, trong khi nó in ra là 7 trên console. Tương tự, có sự gia tăng độ dài của một trong mỗi lệnh mà chúng tôi đã gửi. 

Lý do chính cho điều này là bởi vì netcat gửi giá trị với một newline, và newline cũng chiếm một ký tự. Vì vậy, điều tiếp theo mà chúng tôi cần làm là thêm "\ n" vào cuối mỗi chuỗi mà chúng tôi phân tách ở trên tức là whoami, pwdexit. Chúng sẽ như thế này:

...

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

...

}

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

...

}

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

...

}

Và bây giờ, nếu chạy nhị phân, nó sẽ phân tích cú pháp tất cả các lệnh đúng cách.

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

Một điều cần lưu ý ở đây là khi chúng tôi viết mã python, chúng tôi không cần phải sử dụng newline bởi vì nó sẽ gửi dữ liệu ở định dạng unicode thô không giống như netcat. Vì vậy, chúng ta sẽ phải loại bỏ newline trong mã C hoặc thêm dòng mới trong cả hai máy chủ python3 và mã phần mềm độc hại C . 

Lý do chính không sử dụng netcat là bởi vì nó không thể xử lý nhiều kết nối đồng thời và khi nó gửi một lệnh, nó sẽ gửi cùng với newline. Nếu chúng ta muốn thực thi bất kỳ Windows API nào sử dụng lệnh này như một đối số, nó sẽ không thực thi lệnh do newline. Chúng tôi cũng có thể xóa newline bằng vòng lặp đơn giản nhưng sẽ không đáng để làm thế. 

Chúng tôi sẽ loại bỏ việc sử dụng netcat trong blog tiếp theo của chúng tôi, nơi chúng tôi sẽ xây dựng máy chủ python3 để xử lý các kết nối TCP luồng và chạy các lệnh dựa trên CMD thông qua đó.

Bây giờ, hãy tiếp tục và hiểu các cờ và tùy chọn của trình biên dịch:

-std=c 11 maldev.cpp -o maldev.exe -s -lws2_32 -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc -static-libgcc


-std = c 11 chỉ ra rằng chúng ta sẽ sử dụng chuẩn C 11. Phiên bản g của tôi là 6.3.0 vào thời điểm viết blog này. Phiên bản g của tôi đi kèm với tiêu chuẩn mặc định là C 17. Biên dịch với C 17 mang lại cho tôi một số tính năng mới như chuyển đổi nhanh từ chuỗi thành int, sử dụng các số trong các điều kiện Case: break, nhưng đồng thời nó làm tăng kích thước của nhị phân. Biên dịch với C 17 đã cho tôi một kích thước 32Kb như kích thước cuối cùng của nhị phân, trong khi C 11 cho tôi kích thước 24Kb. Như vậy, c 11 là cái tôi chọn.


-s gỡ bất kỳ siêu dữ liệu còn lại trong các biểu tượng nhị phân, biểu tượng gỡ lỗi, thông tin về nơi nhị phân được biên soạn, tên người dùng của trình biên dịch, tên máy chủ của trình biên dịch, thư mục nơi nó được biên dịch và v.v…

Mẹo: Khi biên dịch nhị phân cuối cùng, phải luôn luôn đảm bảo bạn thực hiện nó trong một máy ảo mới với cài mới để nó không để lại bất kỳ dấu vết nào của tác giả và môi trường mã hóa

-lsw2_32 thông báo cho trình biên dịch liên kết thư viện lsw2_32.lib với socket cần thiết cho window. Điều này sẽ cho phép phiên bản cũ hơn của các cửa sổ hoạt động với các socket

-Wno-write-strings là cần thiết để chuyển đổi chuỗi thành char. Điều này chỉ hữu ích khi gỡ lỗi nhị phân. Trong quá trình biên dịch cuối cùng, điều này không có ảnh hưởng gì vì chúng tôi sẽ không sử dụng tiêu đề <strings.h> hoặc <iostream>.

-fno-exceptions được sử dụng để xác định trình biên dịch mà nhị phân đó đang biên dịch thì không có các ngoại lệ. Nếu ngoại lệ được kích hoạt, nó sẽ làm chậm thời gian xử lý của việc thực thi nhị phân. Mặc dù ở giai đoạn này, thời gian thực thi quy trình là rất nhỏ, nhưng khi chúng tôi nhập các hàm lớn trong phần mềm độc hại cũng có thể bao gồm các quy trình khai thác, do đó thời gian xử lý có xu hướng bị chậm lại nhiều. Đây là lý do chính chúng tôi không sử dụng ngoại lệ trong phần mềm độc hại của mình.

-fmerge-all-constants sẽ kết hợp tất cả các mảng / số nguyên không đổi và ký tự và khởi tạo chúng ngay từ đầu. Điều này rất hữu ích trong việc tối ưu hóa code vì điều này sẽ làm tăng tốc độ xử lý hoặc so sánh bộ đệm và tìm địa chỉ của bộ đệm trong bộ nhớ một cách nhanh chóng. Tùy chọn này sẽ hợp nhất tất cả các địa chỉ và lưu trữ chúng tại một vị trí đã biết và do đó sẽ nhanh hơn để truy cập vào các vị trí này.

-static-libstdc -static-libgcc được sử dụng để biên dịch các tiêu đề C và C tĩnh với nhau trong một chương trình C . Vì chúng ta sẽ kết hợp cả mã C và C , chúng ta sẽ cần đến hai mã này nhiều. Ngoài ra, khi các liên kết động được thực hiện, nó dễ dàng hơn cho các trình diệt virus để phát hiện nhị phân nào là độc hại. Chúng tôi sẽ không liên kết bất kỳ DLL nào với mã nhị phân của chúng tôi bây giờ. Và vì vậy chúng tôi sẽ yêu cầu liên kết tĩnh

        Trong blog tiếp theo, chúng tôi sẽ xây dựng máy chủ botnet python3 có thể tương tác với (các) bot và sau đó chúng tôi sẽ bắt đầu viết mã Bot chính để xử lý các lệnh khác nhau dưới dạng các hàm C .

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

                                                                                        Theo Bizfly Cloud chia sẻ

>>Có thể bạn quan tâm: Phát triển phần mềm độc hại - The Dark Side: Phần 3

SHARE