%!TEX encoding = IsoLatin
%!TEX main = honeypot_and_generative_ai.tex

This manual is a detailed guide for developers working with SYNAPSE. Its goal is to help them understand the software's architecture, explain how the different modules interact, and offer guidance for extending or contributing to the project.

\section*{Software Architecture}

SYNAPSE comprises different interconnected modules, each vital in its functionality. This section outlines the purpose of each module, breaking down the Python files that compose them and highlighting the interactions between different components. The goal is to give developers a solid foundation for navigating the codebase, enabling them to understand how the system operates quickly.
\\
\dirtree{%
.1 SYNAPSE.
.2 data/.
.2 logs/.
.2 prompts/.
.2 scripts/.
.2 src/.
.2 SYNAPSE-to-MITRE/.
.2 system\_logs\_analysis/.
.2 use\_cases/.
}

\textbf{Note}: The \texttt{configSYNAPSE.sh} script can be used to configure the SYNAPSE project entirely. As described in the User's Manual (see Appendix \ref{appx:users_manual}), the installation process starts by cloning the repository, after which \texttt{configSYNAPSE.sh} must be run to set up the project properly. This script can delete any previous root folder, clone the project again, and create the necessary \texttt{logs/} and \texttt{data/} directories.
Additionally, the code automatically calls the \texttt{downloadGeoLiteDB.sh} script that removes any existing version of the GeoLite2 database and replaces it with the latest one, storing it inside the \texttt{data/} folder. The database can be kept up-to-date by scheduling the \texttt{downloadGeoLiteDB.sh} script alone in a cron task. Finally, the \texttt{configSYNAPSE.sh} script assigns the user \texttt{enea} the necessary ownership of the project folder and adjusts permissions accordingly.

\subsection*{SYNAPSE Module}

The \textbf{SYNAPSE} module forms the project's core, containing the code necessary to simulate a Linux OS terminal and MySQL service by leveraging AI. It integrates the generative AI component to provide intelligent responses, mimicking actual system behaviours. SYNAPSE handles all interactions and data flows, making it crucial to the system's overall functionality. The code for the SYNAPSE module is located inside the \texttt{src/} folder at the project's root.
\\
\dirtree{%
.1 src/.
.2 ai\_requests.py.
.2 client\_data.py.
.2 initializations.py.
.2 simulations.py.
.2 SYNAPSE.py.
}

\subsubsection*{SYNAPSE.py}

The \texttt{SYNAPSE.py} code is the main program launched by the \texttt{startSYNAPSE.sh} container script, which is triggered by the \textit{ForceCommand} directive inside \texttt{sshd\_config} when a user connects via SSH. The primary responsibilities of \texttt{SYNAPSE.py} include managing client information, interactions and session handling. It communicates with \texttt{client\_data.py} to initialise client data, increment the number of connections, and append session duration. Next, \texttt{SYNAPSE.py} calls functions from \texttt{initializations.py} to load the terminal prompt configuration and, if not the first connection, retrieve previous session commands and responses. This step ensures that all necessary data is prepared before the AI-driven terminal simulation starts. Once initialised, the terminal simulation is executed by calling the \texttt{terminal\_simulation()} function from \texttt{simulations.py}. Additionally, \texttt{SYNAPSE.py} handles connection termination by responding to user input such as \textit{CTRL+C} or \textit{CTRL+D}, displaying a \textit{logout} message when appropriate.

\subsubsection*{client\_data.py}

\texttt{client\_data.py} is responsible for managing extracted client data. Upon loading, it retrieves the client IP address as a global variable by calling the \texttt{get\_client\_ip()} function, which extracts the IP from the \texttt{SSH\_CLIENT} environment variable. The \texttt{initialize\_client\_data()} function, invoked by \texttt{SYNAPSE.py}, creates a folder for the client IP inside the \texttt{logs/} directory and initialises a JSON data structure. This includes IP information extracted via the \texttt{get\_client\_ip\_info()} function, port details from the \texttt{SSH\_CLIENT} variable, and geolocation data by calling \\\texttt{get\_client\_geolocation()}. These details, along with the number of connections (initialised to 0) and session durations (initialised as an empty array), are stored in the client's file within its folder. This initialisation is skipped for subsequent connections, and the main program only increments the connection count and logs the session duration.

The \texttt{get\_client\_ip\_info()} function retrieves information about the client IP by calling VirusTotal APIs. It extracts essential information, such as \texttt{total\_votes}, \texttt{whois}, \texttt{reputation}, \texttt{last\_analysis\_stats}, \texttt{regional\_internet\_registry}, and \texttt{as\_owner}, and stores them in a JSON object. Similarly, the \texttt{get\_client\_geolocation()} function uses the GeoLite2 City database to gather details such as \texttt{country}, \texttt{region}, \texttt{city}, \texttt{postal\_code}, \texttt{location}, and \texttt{continent}, which are also stored in a JSON object.

The \texttt{increment\_client\_number\_of\_connections()} function increments the number of connections by one at the start of each session. The \texttt{write\_client\_session\_duration\_in\_seconds()} function calculates session duration by subtracting the start time from the end time and appends it to the array of durations in the client's data file. Lastly, the \\\texttt{get\_count\_classification\_history\_files()} function is a utility that provides the current number of classification files and is called by \texttt{simulations.py}.

\subsubsection*{initializations.py}

The \texttt{initializations.py} code manages initialisations regarding prompts, arguments, and messages for the AI in both the terminal and MySQL services offered by SYNAPSE. The code operates almost equally for both functionalities. The \texttt{load\_terminal\_prompt()} and \texttt{load\_mysql\_prompt()} functions load the \texttt{terminal\_personality.yml} or \texttt{services\_personality.yml} files from the \texttt{prompts/} directory, respectively, if it is the first connection.
\\
\dirtree{%
.1 prompts/.
.2 terminal\_personality.yml.
.2 services\_personality.yml.
}

If it is not the first connection, these functions load the previous terminal or MySQL histories from the client's IP directory inside the \texttt{logs/} folder. These files contain the respective prompt and logs of all commands and responses of earlier sessions.

The \texttt{parse\_terminal\_argument()} and \texttt{parse\_mysql\_argument()} functions receive the prompts mentioned earlier. They add the terminal or MySQL personality argument to the parser, using the terminal prompt or MySQL prompt as the default value. For the terminal, the AI is instructed to use the current date and IP address for the last login message, while for MySQL, the AI adopts a random ID for log in. The \texttt{load\_terminal\_messages()} and \texttt{load\_mysql\_messages()} functions generate the \texttt{messages} structure that is passed to the AI, ensuring that it is correctly formatted with \textit{role} and \textit{content} fields for OpenAI API calls. These functions initialise the \texttt{messages} variable with the prompt obtained earlier.

\subsubsection*{simulations.py}

The \texttt{simulations.py} code manages the terminal simulation and initiates and manages the MySQL one. The \texttt{terminal\_simulation()} function oversees the entire terminal simulation. It runs an infinite loop simulating the exchange of commands and responses between the user and the terminal. Within this loop, the last command issued by the user is extracted from the messages and parsed to check if it is an \textit{exit} command, a \textit{clear} command, or a \textit{mysql} command. If none of these conditions apply, the command is sent to the AI by calling the \texttt{generate\_response()} function from \texttt{ai\_requests.py}. The AI's response is appended to the messages and written to the main terminal log file in the respective directory inside \texttt{logs/}.
Additionally, the commands and responses for the session are stored in a separate \texttt{classification\_history} file located in the same client's IP folder. The \texttt{SYNAPSE-to-MITRE} module will later analyse this file. Later in the simulation, the user is prompted to input a command, which is logged in all the files above and updated in the messages variable. The cycle then restarts.

The simulation gracefully closes the connection if the user inputs an \textit{exit} command. If a \textit{clear} one is issued, the system's \texttt{clear} call is executed. In the case of a \textit{mysql} command, the \texttt{run\_mysql\_simulation()} function is called. This function checks the required parameters for authenticating to the MySQL service, such as \textit{-u}, \textit{-p}, and \textit{-e}. If any issues are found, appropriate error messages are displayed. Once the parameters are validated, the MySQL prompt is loaded using functions from \texttt{initializations.py}, and the messages variable for MySQL is set up. The MySQL simulation is then initiated using the \texttt{mysql\_simulation()} function, which ensures proper session handling and graceful exits back to the terminal simulation when the user decides to leave the MySQL service.

The \texttt{mysql\_simulation()} function operates similarly to the \texttt{terminal\_simulation()}, including handling cases where the user wants to execute an inline MySQL command from the terminal with the \textit{-e} flag. The MySQL loop terminates in this scenario after the AI processes the command. The remaining code in \texttt{simulations.py} manages AI responses, ensuring that outputs are corrected when necessary. For instance, delays between output lines are added when executing commands, such as \texttt{ping}, to simulate natural system behaviour.

\subsubsection*{ai\_requests.py}

The \texttt{ai\_requests.py} code handles communication with the AI by leveraging OpenAI APIs. The \texttt{generate\_response()} function generates an AI response based on the prompt passed to it. After receiving the AI's output, the function parses and cleans the response to ensure it is well-structured. It then builds the assistant's message, which is returned to the caller.
Additionally, the \texttt{generate\_tab\_completions()} and \texttt{completer()} functions are used to implement tab autocompletion. These functions work with the AI and the \texttt{readline} module to provide intelligent autocompletion suggestions based on the context of the user's input.

\subsection*{SYNAPSE-to-MITRE Module}

The \textbf{SYNAPSE-to-MITRE} module is an extension of the SYNAPSE project. It enables mapping classification files generated from SYNAPSE interactions to the MITRE ATT\&CK framework using AI and ML technologies. This module can be activated as needed and provides functionality to map observed behaviours to known tactics, techniques, and procedures (TTPs) within the MITRE ATT\&CK framework. In addition to the classification mapping, the module includes code for re-generating the dataset used for training and re-training the machine learning model.
\\
\dirtree{%
.1 SYNAPSE-to-MITRE/.
.2 data/.
.3 capec/.
.3 enterprise-attack/.
.3 dataset.csv.
.2 ml\_model/.
.3 MLP\_classifier.sav.
.2 build\_dataset.py.
.2 classification.py.
.2 SYNAPSE-to-MITRE.py.
.2 training.py.
}

\subsubsection*{SYNAPSE-to-MITRE.py}

The \texttt{SYNAPSE-to-MITRE.py} script can be launched using the container script \\\texttt{startSYNAPSE-to-MITRE.sh}. It contains the core code for mapping SYNAPSE interaction logs to the MITRE ATT\&CK framework. When executed, \texttt{SYNAPSE-to-MITRE.py} scans all the folders inside the \texttt{logs/} directory at the project root, processing one IP folder at a time. For each IP, the reputation is retrieved. Then, the classification history files stored for the various sessions of that IP are analyzed sequentially in chronological order. For each classification file, the reputations of IPs and domains that appear in the commands issued by the user are extracted using the \texttt{get\_ips\_and\_domains\_from\_classification\_file()}, \texttt{get\_ip\_reputation()}, and \texttt{get\_domain\_reputation()} functions from \texttt{classification.py}.

At this point, the \texttt{attack\_happened()} function from \texttt{classification.py} is called to determine whether an attack has occurred. If no attack is detected, the classification file is removed. If an attack is confirmed, the mapping process continues. The \texttt{get\_sentence()} function from \texttt{classification.py} is invoked to generate a summary sentence of the entire classification history file. Next, the \texttt{get\_attack\_objects()} and \texttt{print\_attack\_objects\_to\_file()} functions from \texttt{classification.py} are called to retrieve the three possible mappings related to that sentence and print them to three distinct files. These files are stored alongside the analyzed classification file. This procedure is repeated for each classification file of every IP that has interacted with SYNAPSE.

\subsubsection*{classification.py}

The \texttt{classification.py} file contains all the functions responsible for the logic of the mapping process within the SYNAPSE-to-MITRE module. The \\\texttt{get\_ips\_and\_domains\_from\_classification\_file()} function processes the entire passed classification history file. Using regular expressions to detect IP addresses and domain names, the file is analyzed line by line to identify potential IPs and domains. The \texttt{get\_ip\_reputation()} and \texttt{get\_domain\_reputation()} functions then extract the reputation of the identified IPs and domains by leveraging the VirusTotal APIs.

The \texttt{attack\_happened()} function receives the classification file and all the extracted reputations. Using the entire session history, this function prompts the AI to determine whether an attack occurred based on the commands, responses, and reputations. The AI's decision is generated by calling the \texttt{generate\_response()} function from the \texttt{ai\_requests.py} code in the SYNAPSE module. The \texttt{get\_sentence()} function scans the entire classification history file and, once again using the \texttt{generate\_response()} function, generates an unstructured Cyber Threat Intelligence (CTI) sentence summarizing the entire interaction. This sentence is designed to resemble the sentences in the dataset for training the model.

The \texttt{get\_classifications()} function receives the generated sentence and classifies it into three possible ATT\&CK ID labels. They are then passed to the \texttt{get\_attack\_objects()} function, which extracts the corresponding attack objects by leveraging the \textit{mitreattack} Python library and the \texttt{enterprise-attack} folder inside the \texttt{SYNAPSE-to-MITRE/data/} directory. The \texttt{print\_attack\_objects\_to\_file()} function prints the content of these attack objects to files, storing them in an attack directory alongside the classification history file for future analysis.
Additionally, the \texttt{rename\_classification\_history()} and \texttt{remove\_classification\_history()} functions are utility functions used to rename and remove classification history files, respectively.

\subsubsection*{build\_dataset.py}

The \texttt{build\_dataset.py} script is used to recreate the entire dataset from scratch. The process can be initiated by running the \texttt{startDatasetBuild.sh} container script. To successfully re-build the dataset, the \texttt{capec/} and \texttt{enterprise-attack/} folders inside the \texttt{SYNAPSE-to-MITRE/data/} directory need to be updated with the latest versions. Once done, the script will re-generate the dataset and save it in the same directory under the name \texttt{dataset\_new.csv}. The file must be renamed to \texttt{dataset.csv} to use the newly generated dataset when re-building the model, replacing the previous version.

\subsubsection*{training.py}

Using a new dataset, the \texttt{training.py} script is used to re-train the machine learning model from scratch. The process can be initiated by running the \texttt{startModelTraining.sh} script. This program starts by loading the \texttt{data/dataset.csv} file and uses it to train an \texttt{MLPClassifier} model. Once the training is complete, the resulting model is saved in the \texttt{SYNAPSE-to-MITRE/ml\_model/} directory. Before running the \texttt{training.py} script, deleting any existing model file from the \texttt{ml\_model/} directory is essential to ensure that the new one replaces the old one.

\subsection*{Case Studies Modules}

The following modules are project components primarily representing case studies conducted during different experiments. They showcase practical applications of the SYNAPSE system and demonstrate its behaviour in various scenarios. Among them is the \textbf{DENDRITE} project, an integral part of the case studies. Its code can be found at the following GitHub repository: \url{https://github.com/eneagizzarelli/DENDRITE}.

\subsubsection*{use\_cases}

The \texttt{use\_cases} folder contains programs that simulate AI interactions with the SYNAPSE system. These programs, named \texttt{AI\_vs\_SYNAPSE\_basic\_interaction.py} and \\\texttt{AI\_vs\_SYNAPSE\_attacker\_interaction.py}, showcase different AI-driven interactions.
\\
\dirtree{%
.1 use\_cases/.
.2 AI\_vs\_SYNAPSE\_attacker\_interaction.py.
.2 AI\_vs\_SYNAPSE\_basic\_interaction.py.
}

The \texttt{AI\_vs\_SYNAPSE\_basic\_interaction.py} script leverages the \textit{paramiko} library to connect to a locally running instance of SYNAPSE. It simulates the behaviour of a benign user, interacting with the honeypot by navigating directories, creating files, and performing similar tasks. The code operates in an infinite loop, which can be stopped by SYNAPSE or by the user by pressing \textit{CTRL+C}. In this code, the last command outputted by SYNAPSE is captured and passed to the AI using the \texttt{generate\_response()} function, as previously described. The AI's response is cleaned and appended to the list of messages for the current interaction. The resulting message is then sent back to SYNAPSE, with a brief pause introduced to allow it to generate its response sequentially and ensure the interaction proceeds smoothly.

On the other hand, the \texttt{AI\_vs\_SYNAPSE\_attacker\_interaction.py} script simulates a more aggressive interaction. It operates under a different logic, starting with a \textit{while} loop that continues until a pre-defined number of attacks, set in the \texttt{attacks\_num} variable, is reached. Like the basic interaction, \texttt{paramiko} connects to SYNAPSE. An infinite loop is started to simulate terminal interaction within each attack iteration. It can be stopped by SYNAPSE, by the user (again using \textit{CTRL+C}), or by the AI itself if it determines the attack has ended. If the AI concludes the attack, it prints "Finished", and the Python code breaks the loop.
Additionally, the latter can be interrupted after a pre-defined number of AI-issued commands, currently set to 15, to prevent the AI from entering an infinite loop without terminating the interaction. The rest of the process mirrors the basic interaction, where messages are processed and sent to SYNAPSE, and responses are captured sequentially.

\subsubsection*{system\_logs\_analysis}

The \texttt{system\_logs\_analysis} module allows for AI-powered analysis of system logs, specifically \texttt{auth.log}, \texttt{kern.log}, and \texttt{syslog}, providing a summary of the essential details and information extracted from them.
\\
\dirtree{%
.1 system\_logs\_analysis/.
.2 analyze\_system\_logs.py.
}

The analysis process can be started by running the \texttt{startSystemLogsAnalysis.sh} script, which invokes the \texttt{analyze\_system\_logs.py} code. Within it, three variables control the total number of lines to read from each log file: \texttt{AUTH\_NUM\_LINES}, \texttt{KERN\_NUM\_LINES}, and \texttt{SYSLOG\_NUM\_LINES}. They can be modified to avoid overloading the AI with entire log files, which could be too large to process efficiently. The analysis follows the same sequence for each log: first \texttt{auth.log}, then \texttt{kern.log}, and finally \texttt{syslog}. While the process is the same, the prompt given to the AI is customized for each of them. This is necessary because each log type has different characteristics, and the AI needs to focus on different aspects depending on the log being analyzed. The respective file is copied from the \texttt{/var/log/} directory and is truncated to include only the last pre-defined number of lines. The cut file, along with a log-specific prompt, is then passed to the \texttt{generate\_response()} function in \texttt{ai\_requests.py}. The AI's analysis is subsequently written to an output file. This process is repeated for each log in sequence.

\section*{Adding Services}

For developers interested in expanding the functionality of SYNAPSE, this section provides instructions on how to add new services. It offers clear steps to ensure extensions are smoothly incorporated into the existing structure without disrupting the system's stability.

The first step to add a new service is to modify the \texttt{services\_personality.yml} file in the root folder's \texttt{prompts/} directory. This YAML file has the following structure:

\begin{verbatim}
services:
  mysql:
    prompt:
      ...
\end{verbatim}

A new service can be added by specifying the name and defining a new \texttt{prompt} under it. The latter should describe how the AI should behave while simulating the new service. It is essential to use effective prompt engineering techniques, such as Persona prompting and Few-Shot prompting, to achieve optimal performance. 

After defining the prompt for the new service, several functions in \texttt{initializations.py} must be modified or added to support the new service. Specifically, the following functions should be implemented to follow the same structure as those for \texttt{terminal} and \texttt{mysql}:

\begin{itemize}
    \item \texttt{load\_service\_prompt()}: loads the prompt for the new service, similar to how terminal and MySQL prompts are loaded.
    \item \texttt{parse\_service\_argument()}: parses the service argument from the command input to provide the correct personality and behaviour to the AI.
    \item \texttt{load\_service\_messages()}: prepares the message structure to be passed to the AI, following the format for terminal and MySQL messages.
\end{itemize}

Once the necessary functions are implemented in \texttt{initializations.py}, the \texttt{simulations.py} file must be updated. In the \texttt{terminal\_simulation()} function, glue code should be added to capture whether the user has input a command to switch to the newly added service. If so, a new \texttt{service\_simulation()} function should be defined, following the same pattern as existing service simulations, like \texttt{mysql\_simulation()}. This glue code should manage all cases the AI cannot handle, allowing for a seamless transition between terminal and service simulations. It should behave closely to a natural system, simulating all its essential functionalities. Additionally, the simulation should handle logging like the MySQL service, creating a dedicated log file for the new service's interactions. By following these steps, developers can ensure that new services are fully integrated into the SYNAPSE system, maintaining consistency in interactions, responses, and logging mechanisms.

\section*{Contributing to the Project}

This manual encourages open-source collaboration by outlining how to contribute to the SYNAPSE project. It provides guidance for setting up the development environment and submitting contributions through pull requests, ensuring that all modifications follow the project's coding standards and maintain a structured development process.

To contribute to SYNAPSE, the first step is to fork the original repository on GitHub. This creates a personal copy of the project under the contributor's account. After forking, the repository can be cloned to the local machine for modification. Creating a new branch for each feature or bug fix is recommended, ensuring the main one remains clean. Once changes are made, they should be committed and pushed to the forked repository. Before submitting any changes, contributors must test the functionality of their modifications. This requires configuring both the OpenAI API and the VirusTotal API, which is essential for running and verifying core features within the SYNAPSE project. Once the changes have been tested, contributors should open a pull request from their forked repository to propose the changes to the original project. It should include a clear description of the modifications made and their purpose. Project maintainers may review the changes and request updates, which can be incorporated directly into the same pull request. Once approved, the pull request will be merged into the original project, combining the contribution into SYNAPSE. By following this process, developers can effectively collaborate on SYNAPSE and contribute to its growth while ensuring consistency and code quality.