Учебное руководство по разработке приложения на Python: часть 2

11 апреля 2024

ID 201594

В этом руководстве описывается реализация обмена данными с Kaspersky CyberTrace в приложении на Python.

В части 1 этого руководства описывается приложение, отправляющее данные в Kaspersky CyberTrace.

В части 2 этого руководства описывается приложение, ожидающее получения событий от Kaspersky CyberTrace.

Введение

Эта часть руководства посвящена реализации приложения Python, прослушивающего порт в ожидании событий от Kaspersky CyberTrace.

Имя для приложения можно выбрать произвольно. В примерах в этом руководстве для этого приложения используется имя файла listen_events_cybertrace.py.

О настройках подключения и форматах событий

Приложение будет прослушивать указанный адрес и порт, принимать входящие соединения от Kaspersky CyberTrace и выполнять парсинг полученных данных.

Для определения адреса и порта, на которые отправляются события, в Kaspersky CyberTrace используется параметр OutputSettings > ConnectionString. Адрес и порт для исходящих событий можно просмотреть и настроить на странице Service settings в веб-интерфейсе CyberTrace.

Для определения формата событий Kaspersky CyberTrace использует форматы из раздела OutputSettings конфигурационного файла Kaspersky CyberTrace Service. Каждое событие заканчивается символом новой строки (\n). Форматы событий можно настроить на странице Event format settings в веб-интерфейсе CyberTrace.

1-й этап. Определение функции main()

На этом этапе выполняются следующие действия:

  1. Импорт модуля socket.

    Функции из этого модуля используются для установления соединения с Kaspersky CyberTrace и получения данных.

  2. Определение функции main().
  3. В переменных LISTEN_ADDR и LISTEN_PORT необходимо указать адрес и порт, которые приложение будет прослушивать в ожидании входящих событий.

    Эту информацию можно получить в веб-интерфейсе CyberTrace на странице Service settings, в области Service sends events to.

    import socket

     

    LISTEN_ADDR = "192.0.2.105"

    LISTEN_PORT = 9998

     

    def main():

    pass

    if __name__ == '__main__':

    main()

2-й этап. Добавление код сервера

На этом этапе выполняются следующие действия:

  1. Добавление в функцию main() кода, запускающего сервер, прослушивающий указанные адрес и порт.

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    print("Starting on {}:{}".format(LISTEN_ADDR, LISTEN_PORT))

    server_socket.bind((LISTEN_ADDR, LISTEN_PORT))

    server_socket.listen()

  2. Добавление кода, обрабатывающего входящие соединения.

    Функция socket.accept() принимает входящее соединение. Она возвращает новый объект сокета для соединения (в переменной connection) и адрес на другом конце, инициировавший соединение (в переменной client_address).

    На этом этапе приложение игнорирует данные, полученные в результате соединений. Парсинг будет реализован на следующем этапе.

    Приложение запускает бесконечный цикл. Его можно завершить стандартной комбинацией клавиш или командой операционной системы. В большинстве операционных систем это ^C или Ctrl+C.

    while True:

    connection, client_address = server_socket.accept()

    try:

    print("Connection from {}".format(client_address))

    # Сюда добавляется парсинг данных

    finally:

    connection.close()

3-й этап. Парсинг полученных данных

На этом этапе выполняются следующие действия:

  1. В блоке try... finally вызывается функцию, которая выполняет парсинг полученных данных на события.

    Поскольку в рамках каждого соединения может быть отправлено любое количество событий, приложение должно продолжать парсинг полученных данных, пока не будут обработаны все события. Функция parse_response() возвращает генератор. Затем этот генератор используется в цикле for для итерации по полученным событиям. На следующем шаге реализуется функция parse_response().

    try:

    print("Connection from {}".format(client_address))

    for event in parse_response(connection):

    print("Received:\n{}".format(event.decode()))

    finally:

    connection.close()

  2. Реализация функции parse_response().

    События Kaspersky CyberTrace оканчиваются символом новой строки (\n). Функция parse_response() считывает данные из сокетного соединения в буфер до тех пор, пока в буфере не окажется символ новой строки. Затем функция выделяет событие из буфера и возвращает его. Функция chunk_receive() выполняет итерацию по данным из сокетного соединения. Эта функция будет реализована на следующем этапе.

    Когда цикл for из функции main() переходит к следующей итерации, функция parse_response() выделяет из буфера очередное событие и возвращает его. Если это невозможно, функция считывает дополнительные данные из соединения в буфер. Процесс повторяется до тех пор, пока из соединения не перестанут поступать данные и все события в буфере не будут возвращены.

    def parse_response(connection):

    buff = b''

    for data in chunk_receive(connection):

    buff += data

    while b'\n' in buff:

    event, buff = buff.split(b'\n',1)

    yield event

  3. Реализация функции chunk_receive().

    Функция chunk_receive() считывает 4096 байт данных из соединения и возвращает эти данные. Функция возвращает данные до тех пор, пока в соединении не закончатся данные.

    Функция socket.recv() получает данные из соединения. Если данных нет, функция ожидает получения новых данных. Функция возвращает ноль байтов при закрытии соединения, поэтому цикл while завершается при выполнении этого условия.

    def chunk_receive(connection):

    data = None

    while data != b'':

    data = connection.recv(4096)

    yield data

Этап 5. (Необязательный) Изменение приложения, отправляющее события

Если требуется отправлять события в Kaspersky CyberTrace с помощью приложения, реализованного в части 1 этого руководства, измените приложение так, чтобы события отправлялись без ожидания ответа.

На этом этапе выполняются следующие действия:

  1. Закомментируйте или удалите строку в коде приложения, которая отправляет флаги. Теперь Kaspersky CyberTrace отправляет события приложению-слушателю, поэтому флаг X-KF-ReplyBack становится излишним.
  2. Закомментируйте или удалите строки, которые получают и выводят ответ.

Ниже приведен блок try... finally из приложения с закомментированными строками.

try:

ct_socket.connect((CYBERTRACE_ADDR, CYBERTRACE_PORT))

#ct_socket.sendall(b'X-KF-SendFinishedEventX-KF-ReplyBack')

for event in events:

print("Sending:\n{}".format(event))

ct_socket.sendall(event.encode())

#response = ct_socket.recv(16384)

#print("Response:\n{}".format(response.decode()))

finally:

ct_socket.close()

 

6-й этап. Запуск приложения

Запуск приложения из консоли:

python3 ./listen_events_cybertrace.py

Приложение прослушивает порт в ожидании событий обнаружения киберугроз и оповещений о событиях из Kaspersky CyberTrace и выводит их в консоль.

Если требуется отправлять события в Kaspersky CyberTrace с помощью приложения, разработанного в части 1 этого руководства, запустите это приложение из консоли:

python3 ./send_events_cybertrace.py

Ниже приведен пример вывода приложения.

Starting on 192.0.2.105:9998

Connection from ('192.0.2.42', 41882)

Received:

- category=KL_Malicious_Hash_MD5 matchedIndicator=776735A8CA96DB15B422879DA599F474 url=- src=- ip=- md5=776735A8CA96DB15B422879DA599F474 sha1=- sha256=- usrName=- confidence=100 MD5=776735A8CA96DB15B422879DA599F474 SHA1=3B66A1D70562E291DA023E87B349DD89DFE00213 SHA256=1963CBCBB9FDAAD45F782FAAA467EE2C115C3111C003AE14D01181880B03F6ED file_size=1431 first_seen=10.07.2015 23:53 last_seen=14.07.2020 13:35 popularity=1 threat=HEUR:Trojan.Win32.Generic

Received:

- category=KL_IP_Reputation matchedIndicator=192.0.2.1 url=- src=- ip=192.0.2.1 md5=- sha1=- sha256=- usrName=- confidence=100 category=test first_seen=01.01.2017 00:00 ip=192.0.2.1 ip_geo=ru last_seen=17.07.2020 09:02 popularity=1 threat_score=75

Received:

- category=KL_Malicious_Hash_MD5 matchedIndicator=FEAF2058298C1E174C2B79AFFC7CF4DF url=- src=- ip=- md5=FEAF2058298C1E174C2B79AFFC7CF4DF sha1=- sha256=- usrName=- confidence=100 MD5=FEAF2058298C1E174C2B79AFFC7CF4DF SHA1=D01D17F6B13C7255A234F558ED85078EA5DD3F3D SHA256=4CA914C9791CF2BF2AC69F9A2B21006F0361E247F2CE92F0A9F166DBC6B43670 file_size=1989 first_seen=10.07.2015 23:53 last_seen=14.07.2020 13:35 popularity=1 threat=HEUR:Trojan.Win32.Generic

Received:

- category=KL_IP_Reputation matchedIndicator=192.0.2.3 url=- src=- ip=192.0.2.3 md5=- sha1=- sha256=- usrName=- confidence=100 category=test first_seen=15.01.2017 00:00 ip=192.0.2.3 ip_geo=ru last_seen=17.07.2020 08:51 popularity=1 threat_score=75

Полный код для части 2

Ниже приведен полный код для части 2 этого руководства.

import socket

 

LISTEN_ADDR = "192.0.2.105"

LISTEN_PORT = 9998

 

def chunk_receive(connection):

data = None

while data != b'':

data = connection.recv(4096)

yield data

 

def parse_response(connection):

buff = b''

for data in chunk_receive(connection):

buff += data

while b'\n' in buff:

event, buff = buff.split(b'\n',1)

yield event

 

def main():

 

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print("Starting on {}:{}".format(LISTEN_ADDR, LISTEN_PORT))

server_socket.bind((LISTEN_ADDR, LISTEN_PORT))

server_socket.listen()

 

while True:

connection, client_address = server_socket.accept()

try:

print("Connection from {}".format(client_address))

for event in parse_response(connection):

print("Received:\n{}".format(event.decode()))

finally:

connection.close()

 

if __name__ == '__main__':

 

main()

Вам помогла эта статья?
Что нам нужно улучшить?
Спасибо за ваш отзыв, вы помогаете нам становиться лучше!
Спасибо за ваш отзыв, вы помогаете нам становиться лучше!