Jak pisać skrypty do Spartana?

Czym jest Spartan?

Spartan jest tworzonym przez nas skanerem portów. Napisaliśmy już na jego temat krótki artykuł, z którym zachęcamy się zapoznać, tak samo jak z repozytorium projektu.

Co dają nam skrypty?

Dzięki skryptom w Spartanie możesz w bardzo prosty sposób uzupełnić podstawowe funkcje skanera, zautomatyzować swoją pracę, oraz przedstawić dane w bardziej odpowiedni dla Ciebie sposób.

Jak napisać skrypt do Spartana?

Aby zrozumieć, jak napisać skrypt do Spartana posłużymy się przykładowym skryptem implementującym jedną z metod TLS fingerprintingu — JA3S opisaną w innym naszym poście. Poniżej znajduje się kod skryptu, który wysyła pakiet ClientHello, jeżeli cel, który skanujemy, posiada otwarty port 443:

import socket
for x in result:
    if x['port'] = 443:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((host, x['port']))

	    s.sendall(b"\x16\x03\x01\x00\xa5\x01\x00\x00\xa1\x03\x03\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x00\x00\x20\xcc\xa8\xcc\xa9\xc0\x2f\xc0\x30\xc0\x2b\xc0\x2c\xc0\x13\xc0\x09\xc0\x14\xc0\x0a\x00\x9c\x00\x9d\x00\x2f\x00\x35\xc0\x12\x00\x0a\x01\x00\x00\x58\x00\x00\x00\x18\x00\x16\x00\x00\x13\x65\x78\x61\x6d\x70\x6c\x65\x2e\x75\x6c\x66\x68\x65\x69\x6d\x2e\x6e\x65\x74\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x18\x00\x19\x00\x0b\x00\x02\x01\x00\x00\x0d\x00\x12\x00\x10\x04\x01\x04\x03\x05\x01\x05\x03\x06\x01\x06\x03\x02\x01\x02\x03\xff\x01\x00\x01\x00\x00\x12\x00\x00")
        raw = s.recv(4096)

        import struct

        u_iter = 0
        def unpack_next(fmt):
            sz = struct.calcsize(fmt)
            global u_iter
            val = struct.unpack(fmt, raw[u_iter:u_iter+sz])
            u_iter += sz
            return val

        record_type = unpack_next("B")[0]
        record_protocol_version_major, record_protocol_version_minor = unpack_next("B")[0], unpack_next("B")[0]
        record_data_length = unpack_next(">H")[0]
        print("record_type", record_type)
        print("record_protocol_version", (record_protocol_version_major, record_protocol_version_minor))
        print("record_data_length", record_data_length)

        handshake_type = unpack_next("B")[0]
        unpack_next("B")
        handshake_data_length = unpack_next(">H")[0]
        print("handshake_type", handshake_type)
        print("handshake_data_length", handshake_data_length)

        server_version_major, server_version_minor = unpack_next("B")[0], unpack_next("B")[0]
        random = unpack_next("32B")
        session_id_length = unpack_next("B")[0]
        session_id = unpack_next(str(session_id_length) + "B")
        cipher_suite = unpack_next("B")[0], unpack_next("B")[0]
        compression_method = unpack_next("B")[0]
        extensions_length = unpack_next(">H")[0]
        extensions = []

        end = u_iter + extensions_length
        while u_iter < end:
            type_ = unpack_next(">H")[0]
            info_length = unpack_next(">H")[0]
            info = unpack_next(str(info_length) + "B")
            extensions.append((type_, info_length, info))

        print("sever_version", (server_version_major, server_version_minor))
        print("random", random)
        print("session_id_length", session_id_length)
        print("session_id", session_id)
        print("cipher_suite", cipher_suite)
        print("compression_method", compression_method)
        print("extensions_length", extensions_length)
        print("extensions")
        for ext in extensions:
            print(f"\t{ext}")

        ja3s = str(server_version_major << 8 | server_version_minor) + "," + str(cipher_suite[0] << 8 | cipher_suite[1]) + ","
        ja3s += "-".join([str(e[0]) for e in extensions])
        import hashlib
        ja3s = hashlib.md5(ja3s.encode("ascii")).hexdigest()
        print("\nJA3S", ja3s)

Co się w tym kodzie dzieje?

Tak opowiadałem w poprzednim artykule, skrypt napisany jest w Pythonie. To znaczy, że nic nie stoi na przeszkodzie, aby używać dowolnej biblioteki, usprawniając kod, aby mógł robić bardziej złożone operacje. Oprócz tego znajduje się kilka “spartanowych” zależności:

  • W module lib.helpers.helpers — znajdują się funkcje służące do np. lepszego formatowania tekstu.
for x in result:
  • result — jest listą, w której przetrzymywane są wyniki skanowania wykonanego przez Spartana. Przykładowa struktura listy:
[{'type': 'TCP', 'port': 443, 'status': 'OPEN', 'service': 'https', 'info': None}]
  • host - to adres IP urządzenia, które skanowaliśmy.

Skoro mamy już omówione niestandardowe aspekty tego skryptu, skupmy się na tym, co ten kod robi:

  1. Importowanie niezbędnych modułów:
    Na początku kod importuje moduły socket oraz struct. Pierwszy umożliwia tworzenie połączeń sieciowych, drugi natomiast odpowiada za analizę danych binarnych.
import socket
import struct
  1. Tworzenie połączenia:
    Skrypt nawiązuje połączenie TCP z serwerem, który wcześniej skanowany był przez Spartana po porcie 443 (domyślny port dla protokołu HTTPS).
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, x['port']))
  1. Wysłanie pakietu:
    Tutaj skrypt wysyła dane binarne (b"\x16\x03\x01\x00\xa5...") do serwera za pomocą wcześniej utworzonego połączenia.
s.sendall(b"\x16\x03\x01\x00\xa5\x01\x00\x00\xa1\x03\x03\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x00\x00\x20\xcc\xa8\xcc\xa9\xc0\x2f\xc0\x30\xc0\x2b\xc0\x2c\xc0\x13\xc0\x09\xc0\x14\xc0\x0a\x00\x9c\x00\x9d\x00\x2f\x00\x35\xc0\x12\x00\x0a\x01\x00\x00\x58\x00\x00\x00\x18\x00\x16\x00\x00\x13\x65\x78\x61\x6d\x70\x6c\x65\x2e\x75\x6c\x66\x68\x65\x69\x6d\x2e\x6e\x65\x74\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x18\x00\x19\x00\x0b\x00\x02\x01\x00\x00\x0d\x00\x12\x00\x10\x04\x01\x04\x03\x05\x01\x05\x03\x06\x01\x06\x03\x02\x01\x02\x03\xff\x01\x00\x01\x00\x00\x12\x00\x00")   
  1. Odbieranie odpowiedzi:
    Odbieramy odpowiedź od serwera i przechowujemy ją w zmiennej raw.
raw = s.recv(4096)
  1. Analiza i wyświetlanie wyników analizy odpowiedzi:
    Za pomocą funkcji unpack_next() pobieramy kolejne wartości oczekiwanego formatu z danymi binarnymi. Wartości odczytane z danych binarnych są przepisywane do zmiennych, które reprezentują parametry protokołu TLS, a następnie są wypisane na strumień wyjścia konsoli.
def unpack_next(fmt):
	sz = struct.calcsize(fmt)
	global u_iter
	val = struct.unpack(fmt, raw[u_iter:u_iter+sz])
	u_iter += sz
	return val
  
record_type = unpack_next("B")[0]
record_protocol_version_major, record_protocol_version_minor = unpack_next("B")[0], unpack_next("B")[0]
record_data_length = unpack_next(">H")[0]
print("record_type", record_type)
print("record_protocol_version", (record_protocol_version_major, record_protocol_version_minor))
print("record_data_length", record_data_length)
handshake_type = unpack_next("B")[0]
unpack_next("B")
handshake_data_length = unpack_next(">H")[0]
print("handshake_type", handshake_type)
print("handshake_data_length", handshake_data_length)
server_version_major, server_version_minor = unpack_next("B")[0], unpack_next("B")[0]
  1. Generowanie wartości JA3S:
    • W tym miejscu tworzony jest unikalny fingerprint JA3S na podstawie danych odczytanych z parametrów TLS, takich jak wersja serwera i zastosowany zestaw szyfrowania.
    • Funkcja hashlib.md5() oblicza sumę kontrolną dla otrzymanej wartości JA3S.
ja3s = str(server_version_major << 8 | server_version_minor) + "," + str(cipher_suite[0] << 8 | cipher_suite[1]) + ","
ja3s += "-".join([str(e[0]) for e in extensions])
import hashlib
ja3s = hashlib.md5(ja3s.encode("ascii")).hexdigest()
print("\nJA3S", ja3s)

Jak uruchomić skrypty w Spartanie?

Prosta sprawa, aby uruchomić skrypt, należy wywołać spartana z opcją --script, a następnie podać ścieżkę do pliku .py. Jeżeli skrypty zapisane są w folderze scripts wystarczy, że podasz ich nazwę. Przykładowe użycie naszego nowego skryptu:

 ____                       _
/ ___|  _ __    __ _  _ __ | |_   __ _  _ __
\___ \ | '_ \  / _` || '__|| __| / _` || '_ \
 ___) || |_) || (_| || |   | |_ | (_| || | | |
|____/ | .__/  \__,_||_|    \__| \__,_||_| |_|
       |_|

         With great power comes great responsibility

v0.0.6 created by dannyx-hub

==================================================
Spartan start checks ports on 127.0.0.1
Date: 2023-07-23 14:29:12
Scanner options:
port:  single port 443
scan mode: no mode selected
scripts: ['tls_fingerprint.py']
==================================================

Result for 127.0.0.1:
found: 1

TYPE      PORT  STATUS    SERVICE
TCP        443  OPEN      https

==================================================
Spartan execute
tls_fingerprint.py
==================================================
record_type 22
record_protocol_version (3, 3)
record_data_length 93
handshake_type 2
handshake_data_length 89
sever_version (3, 3)
random (68, 167, 149, 130, 24, 93, 47, 236, 58, 45, 53, 41, 110, 23, 27, 204, 135, 147, 67, 40, 146, 112, 246, 204, 68, 79, 87, 78, 71, 82, 68, 1)
session_id_length 32
session_id (215, 142, 157, 180, 126, 48, 162, 137, 24, 180, 97, 164, 250, 234, 198, 173, 158, 185, 79, 93, 42, 192, 171, 170, 133, 106, 131, 126, 229, 175, 17, 29)
cipher_suite (204, 168)
compression_method 0
extensions_length 17
extensions
        (65281, 1, (0,))
        (0, 0, ())
        (11, 4, (3, 0, 1, 2))

JA3S 76d88c75d798a42d6ea08ab2b9006623

Program end in: 0.03s