--- title: Task 2.2 --- # T2.2 Malicious and Anomalous Behaviour Detection – Relation with the device This task aims at verifying if the function actions match the manifest. The output of this task is a list of features, data, or network usage that do not match the manifest and/or known vulnerabilities that attackers can exploit. # Documentation goblin for elf analysis: https://github.com/m4b/goblin criterion for benchmark: https://github.com/bheisler/criterion.rs Control flow graph of a software: https://en.wikipedia.org/wiki/Control-flow_graph Radare2: https://en.wikipedia.org/wiki/Radare2 r2pipe library: https://docs.rs/r2pipe/latest/r2pipe/ object library: https://docs.rs/object/latest/object/ Article for mobile devices (Android): https://we.tl/t-v7KdvVXXkq Add more articles and documentation to this part for the thesis # Context We are working with Iot devices. Let's make an example Within an house we have a router whose main goals consists of: - controlling a series of actuators - communicating with the external world in order to exchange data and services An actuator is a device which performs specific actions, such as turning a lamp on/off. To execute these tasks, an actuator needs a firmware installed on it. We will call that **firmware device**. # Fake firmware device We have a device with a firmware on it written in either C/C++ or Rust. The first step consists of creating a fake firmware device (we are doing research, so really simple) with a series of functionalities: - Access the network - Write data on an hard drive - Turn a lamp on/off (toggle event as option) - Turn a recording process on/off (toggle event as option) A possible idea of fake device in Rust ```rust= fn main() { // Access to network call_network_access(); // Writes a file call_writes_data_on_hard_drive(); // Turn a lamp on turn_on_a_lamp(); // Record voice record_voice(); // Record a video record_video(); } ``` The exact **same** functionalities **must** be implemented in C/C++. Starting from this example, we need to extend the concept introducing a series of APIs contained in a library (or crate in Rust language). Thus, each fake firmware device should use a determined and safe library to perform its actions. We can call this library **libfakedevice** and we can define within it the following APIs: - turn_lamp_on() - record_video() - write_log() - download_file() So the **Rust** code above becomes: ```rust= use libfakedevice::*; fn main() { // Access to network download_file(); // Writes a file write_log(); // Turn on a lamp turn_lamp_on(); // Record a video record_video(); } ``` The **libfakedevice** library should contain more APIs than the ones defined above. The same library should be implemented in C/C++. # Other software to implement - A **binary** **_manifest-producer_**. It analyzes the binary of a firmware device and produces the relative manifest. The JSON-file manifest should be **digitally signed**. - A **bundle creator** is a tool that puts together both the device binary and the manifest in a single binary unit. The most common example would be a `Docker` image where the metadata could be: - Attach labels to an image. Not advised because we have a minimum amount of space to save data in that way - As an external file in the image (better option) - A **_manifest checker_**. It analyzes the firmware that **is going to be** executed in an environment and checks whether it is compliant with the information contained in the manifest created by the **_manifest-producer_**'s. It can contain two modules: - the first module compares API calls present in the manifest (which are the same ones of the binary) telling the user which are the hazards of these APIs. If a user reject some of the functionalities, the binary must not be run. - The second module analyzes whether the rules about security defined by a user (through a GUI available both on mobile or desktop for example) are compliant with the ones defined in the manifest. - A user rule could be: **The device must not access to the network**. If the manifest contains the string **The device access to the network**, the execution of that firmware **must be** blocked. (instead of using string better to use booleans, so we are not tied up to GUI interface or something like that) # Questions 1. How to build a manifest for Malicious and Anomalous Behaviour Detection for a firmware device? 2. How to identify vulnerabilities that attackers can exploit in a C/C++ or Rust device? # Answers 1. Two ways to extract informations from the fake firmware: - Analyzying its binary. In Rust we can use the https://crates.io/crates/object crate, it should also analyze a C++ binary. A possible implementation of this library to read a binary could be the following: 1. **Libraries and modules used:** ```rust= use object::{read::File, Object, ObjectSection}; ``` Import object library for manipulating binary files. 2. **Function extract_information_from_binary:** ```rust= fn extract_information_from_binary(binary_path: &str) { let data = std::fs::read(binary_path).expect("Failed to read binary"); if let Ok(file) = File::parse(&data) { // ... } else { println!("Failed to parse the binary"); } } ``` The function takes as input the path to the binary file and tries to read it using `std::fs::read`. If the read is successful, it attempts to parse the file using the `File::parse` method. If the parse is successful, it starts iterating over the sections and symbols of the file to parse the information. Otherwise, if parsing fails, an error message is printed. 3. **Iteration on file sections:** ```rust= for section in file.sections() { if let Ok(section) = section { // Analyze the binary sections, might look up the executable code and create the CFG for more advanced analysis. // ... } } ``` The code section provides an iteration over the binary file sections. Inside the if let block, you can implement logic to analyze the sections of the binary, for example by creating a Control Flow Graph (CFG). *How can we create it?* *Can Object library do this?* 4. **Iteration on file symbols:** ```rust= for symbol in section.symbols() { if let Ok(symbol) = symbol { if is_api_call(&symbol) { // Extract information about the API call, such as API name, arguments, etc. let api_name = extract_api_name(&symbol); println!("API call found: {:?}", api_info); if analyze_network_access(api_name, &file) { println!(" - Network access detected"); } if analyze_disk_write(api_name, &file) { println!(" - Write to disk detected"); } if analyze_personal_data_usage(api_name, &file) { println!(" - Leakage of personal data detected"); } } } } ``` This block iterates over the symbols in the binary file. For each symbol, it checks if it is an API call using the is_api_call function. If it is an API call, it extracts information using extract_api_information and prints it out. It is in this section of the code that we address the analysis of malicious and anomalous behavior. 5. **Function is_api_call:** ```rust= fn is_api_call(symbol: &object::Symbol<'_>) -> bool { // Implements logic to check if the symbol is associated with an API call. // Can be based on symbol name, section, etc. // Return true if it is an API call, otherwise false. // ... symbol.kind() == object::read::SymbolKind::Function } ``` This function must be implemented to determine whether a symbol is associated with an API call. It is probably based on the symbol name, section, etc. 6. **Function extract_api_information:** ```rust= fn extract_api_name(symbol: &object::Symbol<'_>) -> String { // Implements logic to extract specific information about the symbol associated with an API call. // Can use the symbol name, section, etc., to get relevant information. // Return the extracted information as a string. // ... symbol.name().to_string() } ``` This function should be implemented to extract specific information from the symbol associated with an API call. In the example code, the symbol name is simply returned as a string. 7. **Function main:** ```rust= fn main() { let binary_path = "path/to/binary"; extract_information_from_binary(binary_path); } ``` The main function starts the program by providing the path to the input binary file. ```json= { "network": [ "get_access", "write_network", ], "disk write": [ "write_data", "write_something", ], "vulnerabilites": [ {"api_name": "write_data", "kind": "memory leak, buffer overflow"}, ], } ``` - Analyzing the control flow graph of a software (how to create that?): tools such as radare2 or Ghidra to analyse the control flow of the software. Creating a control flow graph will help to understand how the firmware performs operations and what its main characteristics are. Kind of information: - Features (identify the kind of features necessary for security, e.g. network access, writing to disk, activating actuators) - Data (Identify the kind of data dangerous for security) - Network usage: check whether there are network calls and how they are being used. The Control Flow Graph (CFG) generated during the analysis of the binary could be used to identify the points at which network access or data sending occurs. CFG analysis could also be performed to identify the presence of download_file() (or other specific APIs). It could be integrated into the manifest-checker so that it can identify whether the firmware is attempting to access the network in violation of the rules established in the manifest. To reply to this question, we need to discover how to extract this information and save them in a format. A JSON format will be the best way because it is simpler to parse and to compare among a determined example JSON. 2. Analyze the information extracted in 1. and verify the problems. A possible example could the following one: To each firmware device is associated a manifest with a series of rules. The manifest is created by a **manifest-producer**. This manifest, which is a JSON file, contains this rule: **the firmware device do not use the network** A **manifest-checker** analyzes the downloaded firmware device and if the device uses the network, the execution of the firmware is blocked and an error is arised. # manifest-producer: How does it work? The manifest-producer tool is designed to perform the firmware certification process through ELF binaries analysis. Its primary objective is to ensure firmware integrity and compliance through three key steps: 1. **API Detection:** Firmware developers must provide the ELF binary together with a list of public APIs used in it. This list forms the basis for the analysis, as each API is independently examined to assess its adherence to the intended behaviour. * *This aspect could be improved*. Instead of retrieve the APIs list from a JSON file provided by the firmware developer, in this phase the tool could store [name, start address and end address] for each function, in statically linked case. In this case, is it possible to understand which functions are public APIs and which are not? Is it possible to improve the handling of **mangling** found in binaries written in programming languages other than C? 2. **Behavioral Analysis:** Once the APIs are identified, the tool disassembles its code and searches for system calls and external library functions, evaluating whether the APIs align with the expected behaviour or exhibit undesired characteristics. * *This aspect could be improved*. Sticking to the idea of improving the static case from the previous point, recursive analysis in search of the syscalls of the various external library functions might be more feasible. So we could avoid simply adding the function name to the flow, but we can create a concrete recursive system call flow. 3. **Manifests generation:** Currently, three different JSON manifests are created. 1. Manifest for **basic information**, is a starting point for understanding the firmware. It provides general information about the ELF file, such as its file name, the programming language used, its target architecture, and its dependency linking type, static or dynamic. Additionally, it lists all public APIs identified in the code, providing a preliminary indication of the functionalities offered by the firmware. 2. Manifest for **syscall flow**, provides a detailed overview of the system calls (and currently library functions) associated with each API identified in the firmware. This document provides a structured sequence of the operations performed during the execution of the various public functions. * Starting from this produced data, is it possible to realise a finite-state automata, capable of representing the real behaviour of the API? 3. Manifest for **features**, classifies APIs according to their functionality offering a structured overview of firmware's capabilities. This categorization occurs through a systematic process that evaluates the system calls (and currently also library functions) associated with each API, identifying the tasks performed and grouping them into meaningful categories. The categorization process is based on a predefined set of functional categories, such as file manipulation, network access, device management, encryption. Each category is associated with a set of keywords or substrates that indicate the presence of specific functionality within system calls and library functions. * Could this method of categorisation be improved? ## ELF analysis *The first step* carried out on the binary is the **recovery of the architecture type:** it was designed for greater program flexibility, setting the binary analysis for the correct architecture. At the moment the only support tested is for x86-64. *The second step* is to identify whether the track has been **stripped or not**. These checks are performed to evaluate whether symbol and string information (essential for understanding and debugging) is present in the binary. In more detail, if the class is a 64-bit or 32-bit architecture, the check is performed to determine whether the .symtab or .strtab sections are present: the function returns true if at least one of the crucial sections for the symbology (symbols and strings ) is missing (indicating that the binary has been stripped). Furthermore, if the class is neither 64b nor 32b, the function returns true. This is a conservative approach where we assume that the binary may be stripped, as we do not have enough information to determine this accurately for other ELF classes. If the binary is not removed, the program **searches for specific APIs** in the symbol table (*third step*). A vector is momentarily provided as a list of known APIs to search for. The known APIs detected within the binary are then inserted into the JSON that will be produced, but saved momentarily in the previously mentioned structure. To carry out the analysis of the characteristics of the track, three possible solutions were devised, of which only two were implemented. 1. **Using hexadecimal patterns**: The idea is to load the known system call patterns for a given architecture into a designated structure, disassemble the code, and perform a search. The main obstacle to this solution is the complexity of the code produced, furthermore disassembling code could be a very resource-intensive calculation. Patterns may change for different architectures. **Pros:**: 1. **Specific identification:** Can be effective in identifying specific system calls if patterns are accurate. **Cons:** 1. **Complexity:** Code disassembly can be complex and resource-intensive, affecting overall performance. 2. **Variations between architectures:** Patterns may vary between different architectures, limiting implementation portability. 3. **Incidence of false positives/negatives:** Sensitivity to pattern changes could cause false positives or negatives, compromising reliability. In summary, while this approach may be useful in certain contexts, the complexity of disassembly and the need to adapt to different architectures make its implementation less than ideal in many situations. 2. **System call tracing**: the idea is to perform a dynamic analysis, tracing system calls during the execution of the binary. This solution involves running the binary and producing a file. The code is readable and in general this logic should be applicable across different architectures, as long as strace is available and provides interpretable output. **Pros:** 1. **Dynamic Analysis:** Provides a dynamic analysis approach, capturing system calls during actual execution, offering a realistic view of the program's behavior. 2. **Architecture Agnostic:** This solution should be applicable across different architectures as long as `strace` is available, making it versatile. 3. **Readability:** The code is relatively readable and follows a straightforward logic, making it easy to understand and maintain. 4. **File Output:** The output generated by `strace` can offer detailed insights into the system calls made by the binary. **Cons:** 1. **Dependency on External Tool:** The solution heavily relies on the availability and functionality of `strace`, so it might impact the effectiveness of the analysis. 2. **Performance Impact:** Running `strace` during execution may introduce overhead and affect the performance of the analyzed binary. In summary, this approach offers a practical and readable solution for dynamic analysis with `strace`, but it is not without its challenges, especially in terms of dependencies on external tools and potential impact on performance. In addition, the use of strace could lead the analysis of the binary into a prolonged waiting state if the binary under consideration does not terminate independently and the user performing the analysis does not terminate it manually. 3. **Using a mapping table**: the idea is to analyze the system call section of an ELF file (text section in this case .text, which contains the code), map the system call numbers to respective names and categorize them. The logic itself presents no inherent obstacles to use on different architectures but it is important to check and, if necessary, adapt the mapping table and function logic according to the specifications of each architecture and ELF file format. **Pros:** 1. **Static Analysis:** This approach performs a static analysis of the ELF file, mapping system call numbers to names within the code section, providing insights into the expected behavior. 2. **Architecture Agnostic Logic:** The logic itself is designed to be architecture-agnostic, making it adaptable across different architectures. 3. **Readability:** The code is structured and readable, facilitating understanding and potential modifications. **Cons:** 1. **Potential for Incomplete Mapping:** If a system call is not present in the mapping table, it won't be categorized, potentially leading to incomplete analysis. 2. **Limited System Call Context:** It may lack additional information about the system call, such as parameters or context, providing a more abstracted view. In conclusion, this static analysis approach offers clarity and adaptability but requires careful maintenance of the mapping table, and it might lack certain contextual details about system calls. It can be a robust method when coupled with regular updates to the mapping table. # Interface file This file contains every public API contained in a firmware. It is not fundamental whether the API is called or not by an application which interacts with a firmware, the important thing is that the API is present within the firmware itself. This file is written by a **device producer**, thus a developer who has been working for a determined company i.e. Samsung. As an example we can consider the Android Interface Definition Language to define the grammar of the interface file: https://docs.fileformat.com/it/programming/aidl/ Each API **must** be commented in order to explain its purpose and how it works. Comments are used to split among an API and the next one. Syntax for comments is `/** comment */` One can also add comments to the API arguments using the `/* comment */` syntax. A possible file definition could be: ``` interface DeviceInterface { /** Turns on a device light */ bool device_light_turn_on(); /** Turns off a device light */ bool device_light_turn_off(); /** Save report */ bool save_report( /* report path */ path, ); } ``` If a firmware contains APIs which are not present in the considered interface file, that firmware **must not be** approved. ## Manifest output ```jsonld= turn_light_on: { access_network: [ "systemcall1", ], write_drive: [ "systemcall" ] } ``` ## Note - Introduction to IoT (salient points) - Problems in developing IoT code from a developer perspective (task 2.1, ci-generate) - Certification parameters (task 2.2)