├── LICENSE ├── README.md ├── centrifuge ├── __pycache__ │ ├── binfile.cpython-36.pyc │ ├── binfile.cpython-37.pyc │ ├── dataID.cpython-37.pyc │ └── datablock.cpython-37.pyc ├── binfile.py ├── datablock.py ├── distributions │ ├── cpu_architectures │ │ ├── AMD64_reference │ │ ├── ARM64_reference │ │ ├── ARMEL_reference │ │ ├── MIPS64EL_reference │ │ ├── MIPSEL_reference │ │ ├── PPC64_reference │ │ ├── PowerPC_reference │ │ ├── SH4_reference │ │ └── i386_reference │ └── data_types │ │ ├── archive │ │ └── readme.txt │ │ ├── machine_code │ │ ├── max_entropy │ │ └── utf8_english └── utils │ └── plotutils.py ├── gallery ├── 1.png ├── 10.png ├── 11.png ├── 12.png ├── 13.png ├── 14.png ├── 15.png ├── 16.png ├── 17.png ├── 18.png ├── 19.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png └── 9.png ├── images ├── approach.png ├── approach_2.png └── approach_3.png ├── notebooks ├── Analyzing Firmware with Centrifuge Example 2.ipynb ├── Analyzing Firmware with Centrifuge.ipynb ├── Analyzing Machine Code Targeting an Usupported Architecture.ipynb ├── CPU Architecture Reference Distributions │ ├── Comparing CPU Architecture Reference Distributions.ipynb │ ├── Exploring Machine Code Byte Value Distributions.ipynb │ └── architectures │ │ ├── AMD64 reference distribution construction.ipynb │ │ ├── ARM64 reference distribution construction.ipynb │ │ ├── ARMEL reference distribution construction.ipynb │ │ ├── MIPS64EL reference distribution construction.ipynb │ │ ├── MIPSEL reference distribution construction.ipynb │ │ ├── PPC64 reference distribution construction.ipynb │ │ ├── PowerPC reference distribution construction.ipynb │ │ ├── SH4 reference distribution construction.ipynb │ │ └── i386 reference distribution construction.ipynb ├── Data Type Reference Distributions │ ├── The Machine Code Reference Distribution.ipynb │ ├── The Max Entropy Reference Distribution.ipynb │ └── The UTF-8 (English) Reference Distribution.ipynb ├── Introduction to Centrifuge.ipynb ├── Using DBSCAN to Cluster File Data.ipynb └── archive │ ├── Analyzing Executable Binaries with DBSCAN.ipynb │ └── readme.txt └── scripts ├── basic_DBSCAN_clustering.py ├── entropy_plot.py ├── entropy_plot_text_section.py ├── identify_clusters.py ├── plot_all_variables.py ├── plot_cluster_cdfs.py ├── plot_two_variables.py ├── readme.txt └── small_elf.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Julian Daeumer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Centrifuge 2 | 3 | Centrifuge makes it easy to use visualization, statistics and machine learning to analyze information in binary files. 4 | 5 |
6 | 7 | This tool implements two new approaches to analysis of file data: 8 | 9 | 1. [DBSCAN](https://scikit-learn.org/stable/modules/clustering.html#dbscan), an unsupervised machine learning algorithm, is used to find clusters of byte sequences based on their statistical properties (features). Byte sequences that encode the same data type, e.g. machine code, typically have similar properties. As a result, clusters are often representative of a specific data type. Each cluster can be extracted and analysed further. 10 | 11 | 2. The specific data type of a cluster can often be identified without using machine learning by measuring the [Wasserstein distance](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.wasserstein_distance.html) between its byte value distribution and a data type *reference distribution*. If this distance is less than a set threshold for a particular data type, that cluster will be identified as that data type. Currently, reference distributions exist for high entropy data, UTF-8 english, and machine code targeting various CPU architectures. 12 | 13 | These two approaches are used together in sequence: first DBSCAN finds clusters, then the Wasserstein distances between the clusters' data and the reference distributions are measured to identify their data type. To identify the target CPU of any machine code discovered in the file, Centrifuge uses [ISAdetect](https://github.com/kairis/isadetect). 14 | 15 | ## Required Libraries 16 | 17 | All required libraries come bundled with [Anaconda](https://www.anaconda.com/products/individual). 18 | 19 | *Developed in a Linux environment. Not tested on Windows or MacOS. 20 | 21 | ## Usage 22 | 23 | Detailed walkthroughs can be found in the [notebooks](https://github.com/BinaryResearch/centrifuge/tree/master/notebooks). Code snippets are located in the [scripts](https://github.com/BinaryResearch/centrifuge-toolkit/tree/master/scripts) folder. 24 | 25 | - [Introduction to Centrifuge](https://github.com/BinaryResearch/centrifuge/blob/master/notebooks/Introduction%20to%20Centrifuge.ipynb) provides an overview of Centrifuge's features and a demonstration of how the tool works. 26 | - [Using DBSCAN to Cluster File Data](https://github.com/BinaryResearch/centrifuge-toolkit/blob/master/notebooks/Using%20DBSCAN%20to%20Cluster%20File%20Data.ipynb) shows examples of how to adjust DBSCAN's `eps` and `min_samples` parameters to get the best results. 27 | - [Analyzing Firmware with Centrifuge](https://github.com/BinaryResearch/centrifuge-toolkit/blob/master/notebooks/Analyzing%20Firmware%20with%20Centrifuge.ipynb) and [Analyzing Firmware with Centrifuge Example 2](https://github.com/BinaryResearch/centrifuge-toolkit/blob/master/notebooks/Analyzing%20Firmware%20with%20Centrifuge%20Example%202.ipynb) provide tutorials for analyzing firmware binaries. 28 | - [Analyzing Machine Code Targeting an Usupported Architecture](https://github.com/BinaryResearch/centrifuge-toolkit/blob/master/notebooks/Analyzing%20Machine%20Code%20Targeting%20an%20Usupported%20Architecture.ipynb) discusses what may occur when an executable binary contains machine code targeting a CPU architecture for which there is no matching reference distribution and ISAdetect does not correctly classify it. 29 | 30 | ## Overview of the Approach 31 | 32 | The first step is file partitioning and feature measurement. 33 | 34 | 35 | 36 | DBSCAN can then be used to find clusters in the file data. 37 | 38 | 39 | 40 | Once clusters have been found, the data in the clusters can be identified. 41 | 42 | 43 | 44 | The feature observations of each cluster are stored in a separate data frame, one for each cluster (e.g if 6 clusters are found, there will be 6 data frames, 1 per cluster). The output of DBSCAN is also saved in a data frame. This means custom analysis of any/all clusters can easily be performed any time after DBSCAN identifies clusters in the file data. 45 | 46 | ## Example Output 47 | 48 | Output of `bash.identify_cluster_data_types()`, as seen in [Introduction to Centrifuge](https://github.com/BinaryResearch/centrifuge/blob/master/notebooks/Introduction%20to%20Centrifuge.ipynb): 49 | 50 | ``` 51 | Searching for machine code 52 | -------------------------------------------------------------------- 53 | 54 | [+] Checking Cluster 4 for possible match 55 | [+] Closely matching CPU architecture reference(s) found for Cluster 4 56 | [+] Sending sample to https://isadetect.com/ 57 | [+] response: 58 | 59 | { 60 | "prediction": { 61 | "architecture": "amd64", 62 | "endianness": "little", 63 | "wordsize": 64 64 | }, 65 | "prediction_probability": 1.0 66 | } 67 | 68 | 69 | Searching for utf8-english data 70 | ------------------------------------------------------------------- 71 | 72 | [+] UTF-8 (english) detected in Cluster 3 73 | Wasserstein distance to reference: 16.337275669642857 74 | 75 | [+] UTF-8 (english) detected in Cluster 5 76 | Wasserstein distance to reference: 11.878225097656252 77 | 78 | 79 | Searching for high entropy data 80 | ------------------------------------------------------------------- 81 | 82 | [+] High entropy data found in Cluster 1 83 | Wasserstein distance to reference: 0.48854199218749983 84 | [*] This distance suggests the data in this cluster could be 85 | a) encrypted 86 | b) compressed via LZMA with maximum compression level 87 | c) something else that is random or close to random. 88 | ``` 89 | 90 | ## File Data Visualization 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | More pictures can be found in the [gallery](https://github.com/BinaryResearch/centrifuge-toolkit/tree/master/gallery). 105 | 106 | ## Example Use Cases 107 | 108 | - **Determining whether a file contains a particular type of data.** 109 | 110 | An entropy scan is useful for discovering compressed or encrypted data, but what about other data types such as machine code, symbol tables, sections of hardcoded ASCII strings, etc? Centrifuge takes advantage of the fact that in binary files, information encoded in a particular way is stored contiguously and uses scikit-learn's implementation of DBSCAN to locate these regions. 111 | - **Analyzing files with no metadata such as magic numbers, headers or other format information.** 112 | 113 | This includes most firmware, as well as corrupt files. Centrifuge does not depend on metadata or signatures of any kind. 114 | - **Investigating differences between different types of data using statistical methods or machine learning, or building a model or "profile" of a specific data type.** 115 | 116 | Does machine code differ in a systematic way from other types of information encoded in binary files? Can compressed data be distinguished from encrypted data? These questions can be investigated in an empirical way using Centrifuge. 117 | - **Visualizing information in files using Python libraries such as Seaborn, Matplotlib and Altair** 118 | 119 | Rather than generate elaborate 2D or 3D visual representations of file contents using space-filling curves or cylindrical coordinate systems, Centrifuge creates data frames that contain the feature measurements of each cluster. The information in these data frames can be easily visualized with boxplots, violin plots, pairplots, histograms, density plots, scatterplots, barplots, cumulative distribution function (CDF) plots, etc. 120 | 121 | ## Dataset 122 | 123 | The [ISAdetect dataset](https://etsin.fairdata.fi/dataset/9f6203f5-2360-426f-b9df-052f3f936ed2/data) was used to create the i386, AMD64, MIPSEL, MIPS64EL, ARM64, ARMEL, PowerPC, PPC64, and SH4 reference distributions. 124 | 125 | ## Todo 126 | 127 | - Adding the ability to use [OPTICS](https://scikit-learn.org/stable/modules/generated/sklearn.cluster.OPTICS.html#sklearn.cluster.OPTICS) for automatic clustering. It would be nice to automate the entire workflow, going straight from an input file to data type identification. Currently this is not possible because `eps` and `min_samples` need to be adjusted manually in order ensure meaningful results when using DBSCAN. 128 | - Improving the UTF-8 english data reference distribution. Rather than derive it from text extracted from an ebook, samples should be drawn from hard-coded text data in executable binaries. 129 | - Creating reference distributions for AVR and Xtensa 130 | - update the code with docstrings and comments 131 | -------------------------------------------------------------------------------- /centrifuge/__pycache__/binfile.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/__pycache__/binfile.cpython-36.pyc -------------------------------------------------------------------------------- /centrifuge/__pycache__/binfile.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/__pycache__/binfile.cpython-37.pyc -------------------------------------------------------------------------------- /centrifuge/__pycache__/dataID.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/__pycache__/dataID.cpython-37.pyc -------------------------------------------------------------------------------- /centrifuge/__pycache__/datablock.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/__pycache__/datablock.cpython-37.pyc -------------------------------------------------------------------------------- /centrifuge/binfile.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import pickle 4 | import requests 5 | import matplotlib.pyplot as plt 6 | from scipy.stats import entropy, gamma, relfreq, wasserstein_distance 7 | import numpy as np 8 | import pandas as pd 9 | from pandas.plotting import scatter_matrix 10 | import seaborn as sns; sns.set() 11 | from sklearn.cluster import DBSCAN 12 | from sklearn import metrics 13 | from sklearn.preprocessing import StandardScaler 14 | from sklearn.neighbors import NearestNeighbors 15 | from math import ceil, sqrt 16 | 17 | from centrifuge.datablock import DataBlock 18 | 19 | 20 | 21 | 22 | def find_optimal_eps(matrix, k, epsilon): 23 | """ 24 | Uses kNN distances to plot a curve. This curve can then be used 25 | to choose an optimal value of eps for DBSCAN 26 | """ 27 | nbrs = NearestNeighbors(n_neighbors=k, algorithm='ball_tree').fit(matrix) 28 | distances, indices = nbrs.kneighbors(matrix) 29 | sorted_distances = np.sort(np.concatenate(distances[:, -1:])) 30 | 31 | plt.axhline(y=epsilon, color='red') 32 | plt.text(0, epsilon+0.25, "eps = " + str(epsilon)) 33 | 34 | plt.plot(np.arange(len(sorted_distances)), sorted_distances) 35 | plt.title("K-nearest neighbor distances. Use this plot to choose an optimal epsilon value") 36 | plt.xlabel("index") 37 | plt.ylabel("kNN distances for k = " + str(k)) 38 | plt.ylim(0) 39 | plt.show() 40 | 41 | 42 | 43 | 44 | class BinFile: 45 | def __init__(self, file_handle): 46 | self.file_handle = file_handle 47 | self.pathname = self.file_handle.name 48 | self.block_size = 1024 # default; will be scaled based on file size, and may be updated manually 49 | 50 | self.file_handle.seek(0) 51 | #self.data = self.file_handle.read() # read full file into memory 52 | self.file_handle.seek(0,2) 53 | self.size = self.file_handle.tell() 54 | self.file_handle.seek(0) 55 | 56 | self.debug_level = 0 # default; may be overridden via set_debug_level() 57 | 58 | # 59 | self.blocks = [] 60 | #self.block_offsets = [] 61 | self.block_offsets = None 62 | 63 | self.block_entropy_levels = None # uses log base 2, not log base 10 64 | self.block_zeroes_ratios = None # % bytes in chunk that are 0 65 | self.block_ascii_ratios = None # % bytes in chunk that fall between 32 and 126 inclusive 66 | self.block_byteval_std_dev = None # 67 | self.block_byteval_std_dev_counts = None # std dev of the counts of each byte value, not the values themselves 68 | self.block_byteval_mean = None # 69 | self.block_byteval_median = None # 70 | self.file_data_frame = None # data frame built from np arrays of file chunk information stats 71 | 72 | # DBSCAN results stored in this variable 73 | self.db = None # refers to DBSCAN output 74 | self.dbscan_data_frame = None # file data frame + cluster labels 75 | 76 | ############################### 77 | # methods 78 | 79 | 80 | def seek(self, offset): 81 | self.file_handle.seek(offset) 82 | 83 | 84 | # manually set block size 85 | def set_block_size(self, num_bytes): 86 | self.block_size = num_bytes 87 | 88 | # manually set size of file 89 | def set_size(self, num_bytes): 90 | self.size = num_bytes 91 | 92 | 93 | def slice_file(self): 94 | # number of blocks = file size / block size 95 | 96 | # initialize np arrays 97 | self.block_offsets = np.empty(ceil(self.size / self.block_size), dtype='int64') 98 | self.block_entropy_levels = np.empty(ceil(self.size / self.block_size), dtype='float64') 99 | self.block_zeroes_ratios = np.empty(ceil(self.size / self.block_size), dtype='float64') 100 | self.block_ascii_ratios = np.empty(ceil(self.size / self.block_size), dtype='float64') 101 | self.block_byteval_std_dev = np.empty(ceil(self.size / self.block_size), dtype='float64') 102 | self.block_byteval_std_dev_counts = np.empty(ceil(self.size / self.block_size), dtype='float64') 103 | self.block_byteval_mean = np.empty(ceil(self.size / self.block_size), dtype='float64') 104 | self.block_byteval_median = np.empty(ceil(self.size / self.block_size), dtype='int64') 105 | 106 | offset = 0 # tracks block offsets 107 | for i in range(0, (ceil(self.size / self.block_size))): 108 | new_block = DataBlock(self.pathname, 109 | self.file_handle.read(self.block_size), 110 | self.block_size, 111 | offset) 112 | self.blocks.append(new_block) 113 | 114 | self.block_offsets[i] = new_block.offset 115 | self.block_entropy_levels[i] = new_block.entropy 116 | self.block_zeroes_ratios[i] = new_block.zeroes_ratio 117 | self.block_ascii_ratios[i] = new_block.ascii_ratio 118 | self.block_byteval_std_dev[i] = new_block.byteval_std_dev 119 | self.block_byteval_std_dev_counts[i] = new_block.byteval_std_dev_counts 120 | self.block_byteval_mean[i] = new_block.byteval_mean 121 | self.block_byteval_median[i] = new_block.byteval_median 122 | 123 | offset += self.block_size 124 | 125 | # now that the np arrays have been created, build data frame 126 | self.create_data_frame() 127 | 128 | 129 | # should create a dataframe out of these arrays 130 | def create_data_frame(self): 131 | '''create data frame from lists''' 132 | if self.debug_level > 0: 133 | print("[+] creating data frame") 134 | 135 | self.file_data_frame = pd.DataFrame({'entropy': self.block_entropy_levels, 136 | 'zeroes ratios': self.block_zeroes_ratios, 137 | 'ascii ratios': self.block_ascii_ratios, 138 | 'byte value std dev': self.block_byteval_std_dev, 139 | 'byte value counts std dev': self.block_byteval_std_dev_counts, 140 | 'byte value mean': self.block_byteval_mean, 141 | 'byte value median': self.block_byteval_median}) 142 | 143 | 144 | def show_scatter_matrix(self): 145 | '''plots all columns against each other''' 146 | pd.plotting.scatter_matrix(self.file_data_frame, alpha=0.3, figsize=(20,20), diagonal='kde') 147 | plt.show() 148 | 149 | 150 | 151 | 152 | # TODO: add docstring 153 | #def entropy_vs_zeroes_ratios_quickplot(self): 154 | # 155 | # plt.scatter(self.block_zeroes_ratios, 156 | # self.block_entropy_levels, 157 | # alpha=0.15, 158 | # color='purple') 159 | # 160 | # plt.title('Entropy vs. Ratio of 0x00 Byte Values') 161 | # plt.xlabel('0x00 Byte Ratio') 162 | # plt.ylabel('Entropy') 163 | # plt.xlim(-0.05, 1) 164 | # plt.ylim(0, 8.5) 165 | # 166 | # plt.show() 167 | 168 | 169 | 170 | def plot_variables_by_range(self, x, y, start, end, target_data_marker=None, other_data_marker=None, target_data_color=None, other_data_color=None, title=None, xlabel=None, ylabel=None): 171 | plt.title('test') 172 | 173 | within_range_mask = np.logical_and(self.block_offsets >= start, self.block_offsets <= end) 174 | out_of_range_mask = np.logical_xor(self.block_offsets >= start, self.block_offsets <= end) 175 | 176 | offsets_within_range = self.block_offsets[within_range_mask] 177 | x_within_range = x[within_range_mask] 178 | y_within_range = y[within_range_mask] 179 | 180 | if target_data_marker is None: 181 | target_data_marker = 's' 182 | 183 | if other_data_marker is None: 184 | other_data_marker = 'o' 185 | 186 | if target_data_color is None: 187 | target_data_color = 'red' 188 | 189 | if other_data_color is None: 190 | other_data_color = 'black' 191 | 192 | plt.plot(x_within_range, y_within_range, target_data_marker, color = target_data_color, alpha=0.3) 193 | 194 | offsets_out_of_range = self.block_offsets[out_of_range_mask] 195 | x_out_of_range = x[out_of_range_mask] 196 | y_out_of_range = y[out_of_range_mask] 197 | 198 | plt.plot(x_out_of_range, y_out_of_range, other_data_marker, color = other_data_color, alpha=0.3) 199 | 200 | plt.title(title) 201 | plt.xlabel(xlabel) 202 | plt.ylabel(ylabel) 203 | 204 | plt.show() 205 | 206 | 207 | 208 | 209 | def plot_file_entropy(self, start=None, end=None): 210 | ''' 211 | start and none are numbers representing offsets within the file. 212 | Can be decimal or hexadecimal. 213 | ''' 214 | 215 | try: 216 | plt.axvline(x=start, color='red') 217 | plt.axvline(x=end, color='red') 218 | except TypeError: 219 | pass 220 | 221 | plt.plot(self.block_offsets, 222 | self.block_entropy_levels, 223 | linewidth=0.8, 224 | color='blue') 225 | 226 | plt.title("Entropy of " + self.pathname.split('/')[-1:][0]) 227 | plt.xlabel('Offset') 228 | plt.ylabel('Entropy') 229 | plt.ylim(-0.5, 8.25) 230 | 231 | plt.show() 232 | 233 | 234 | def plot_file_feature(self, feature, color=None, start=None, end=None): 235 | ''' 236 | generalized version of plot_file_entropy 237 | ''' 238 | try: 239 | plt.axvline(x=start, color='red') 240 | plt.axvline(x=end, color='red') 241 | except TypeError: 242 | pass 243 | 244 | 245 | features = {"mean":self.block_byteval_mean, 246 | "median":self.block_byteval_median, 247 | "std_dev":self.block_byteval_std_dev, 248 | "std_dev_counts":self.block_byteval_std_dev_counts, 249 | "entropy":self.block_entropy_levels, 250 | "ascii":self.block_ascii_ratios, 251 | "zeroes":self.block_zeroes_ratios} 252 | 253 | if color is None: 254 | color = "black" 255 | 256 | if feature in features: 257 | plt.plot(self.block_offsets, 258 | features[feature], 259 | linewidth=1.1, 260 | color=color) 261 | else: 262 | print("Feature argument must be one of the following: ") 263 | for feature in features: 264 | print(feature) 265 | 266 | plt.title(self.pathname.split('/')[-1:][0]) 267 | plt.xlabel('Offset') 268 | plt.ylabel(feature) 269 | plt.show() 270 | 271 | 272 | # 273 | def set_debug_level(self, level): 274 | self.debug_level = level 275 | 276 | if self.debug_level > 0: 277 | print("[+]\tDebug level set to %d" % self.debug_level) 278 | 279 | 280 | ##################################################################################### 281 | # Clustering with DBSCAN 282 | ##################################################################################### 283 | 284 | # eps=0.4 and min_sample=10 perform well in general, but 285 | # eps needs to be increased to 0.7 or higher for files smaller than ~100KB 286 | # min_sample needs to be increased to 20, 30 or higher for larger (~3MB+) files 287 | # try finding optimal value of eps using kNN distances 288 | 289 | def cluster_DBSCAN(self, epsilon, minimum_samples, find_optimal_epsilon=True): 290 | """ 291 | return a data frame containing data from clustering results and the data blocks 292 | """ 293 | 294 | X = StandardScaler().fit_transform(self.file_data_frame) # standardize and scale data frame. Using scikit-learn nc 295 | 296 | if (find_optimal_epsilon==True): 297 | find_optimal_eps(X, minimum_samples, epsilon) 298 | 299 | self.db = DBSCAN(eps=epsilon, min_samples=minimum_samples).fit(X) 300 | self.db.n_clusters_ = len(set(self.db.labels_)) - (1 if -1 in self.db.labels_ else 0) 301 | 302 | if self.debug_level > 0: 303 | print("Set of clusters found by DBSCAN: " + str(set(self.db.labels_))) 304 | 305 | core_samples_mask = np.zeros_like(self.db.labels_, dtype=bool) 306 | core_samples_mask[self.db.core_sample_indices_] = True 307 | 308 | # create data frame 309 | db_data_frame = self.file_data_frame.copy(deep=True) 310 | db_data_frame['core samples mask'] = core_samples_mask 311 | db_data_frame['cluster labels'] = self.db.labels_ 312 | 313 | #return db_data_frame 314 | self.dbscan_data_frame = db_data_frame 315 | # 316 | # labels = self.db.labels_ 317 | # n_clusters_ = len(set(labels)) - (1 if -1 in labels else 0) 318 | # 319 | # print("Number of clusters found via DBSCAN: " + str(n_clusters_)) 320 | 321 | #def cluster_DBSCAN(self, epsilon, minimum_samples): 322 | # """ 323 | # Returns DBSCAN object 324 | # """ 325 | # self.db = ClusterDBSCAN(epsilon, minimum_samples, self.file_data_frame) 326 | 327 | 328 | # TODO: outliers (cluster -1) need to be black 329 | def plot_DBSCAN_results(self): 330 | 331 | """ 332 | Refactored. 333 | """ 334 | 335 | #grid = plt.GridSpec(2, 4, wspace=0.4, hspace=0.3) # <-------------- 336 | #plt.subplot(grid[0, 0:]) 337 | #plt.scatter(np.random.random(100), np.random.random(100)) 338 | #plt.subplot(grid[1, 2:]) 339 | #plt.subplot(grid[1, :2]); 340 | 341 | # rainbow_r, prism, Spectral 342 | #plt.subplot(grid[0, 0:]) # <----------- 343 | #plt.plot(self.block_offsets, 344 | # self.block_entropy_levels, 345 | # linewidth=0.3, 346 | # color='black') 347 | #plt.ylim(-0.25, 8.5) 348 | 349 | labels = self.db.labels_ 350 | unique_labels = set(labels) 351 | colors = [plt.cm.rainbow_r(each) 352 | for each in np.linspace(0, 1, len(unique_labels))] 353 | shapes = ['H','D', 's', 'd', 'o', 'v', 'p', 'h', '^', '>', '<', '.'] 354 | 355 | cluster_dfs, _ = self.extract_clusters() 356 | 357 | if cluster_dfs is None: 358 | print("[!] No clusters to plot. Exiting.") 359 | return 360 | 361 | for cluster_id in sorted(cluster_dfs.keys()): 362 | if cluster_id == -1: 363 | color = "black" 364 | else: 365 | color = colors[cluster_id] 366 | 367 | plt.scatter(list(cluster_dfs[cluster_id]["entropy"].index * self.block_size), 368 | cluster_dfs[cluster_id]["entropy"], 369 | edgecolors="k", 370 | marker=shapes[cluster_id], 371 | color=color, 372 | alpha=1) 373 | 374 | plt.ylim(-0.25, 8.25) 375 | plt.title(self.pathname.split('/')[-1:][0]) 376 | plt.xlabel("Block Offset") 377 | plt.ylabel("Block Entropy") 378 | plt.show() 379 | 380 | 381 | 382 | for cluster_id in sorted(cluster_dfs.keys()): 383 | if cluster_id == -1: 384 | color = "black" 385 | else: 386 | color = colors[cluster_id] 387 | 388 | plt.scatter(cluster_dfs[cluster_id]["byte value std dev"], 389 | cluster_dfs[cluster_id]["entropy"], 390 | edgecolors="k", 391 | marker=shapes[cluster_id], 392 | color=color, 393 | alpha=1) 394 | 395 | plt.xlim(-5) 396 | plt.ylim(-0.25, 8.25) 397 | plt.title(self.pathname.split('/')[-1:][0]) 398 | plt.xlabel("Block Byte Value Standard Deviation") 399 | plt.ylabel("Block Entropy") 400 | plt.show() 401 | 402 | 403 | 404 | for cluster_id in sorted(cluster_dfs.keys()): 405 | if cluster_id == -1: 406 | color = "black" 407 | else: 408 | color = colors[cluster_id] 409 | 410 | plt.scatter(cluster_dfs[cluster_id]["byte value median"], 411 | cluster_dfs[cluster_id]["zeroes ratios"], 412 | edgecolors="k", 413 | marker=shapes[cluster_id], 414 | color=color, 415 | alpha=1) 416 | 417 | plt.xlabel("Block Printable ASCII Ratio") 418 | plt.title(self.pathname.split('/')[-1:][0]) 419 | plt.xlabel("Block Median") 420 | plt.ylabel("Block Zeroes Ratio") 421 | plt.show() 422 | 423 | 424 | 425 | def plot_two_features_with_cluster_labels(self, feature_1, feature_2, with_noise=True): 426 | if feature_1 not in self.dbscan_data_frame.columns or feature_2 not in self.dbscan_data_frame.columns: 427 | print("Arguments must be 2 of the following: ") 428 | for feature in self.dbscan_data_frame.columns[0:-2]: 429 | print(feature) 430 | return 431 | 432 | labels = self.db.labels_ 433 | unique_labels = set(labels) 434 | colors = [plt.cm.rainbow_r(each) 435 | for each in np.linspace(0, 1, len(unique_labels))] 436 | shapes = ['H','D', 's', 'd', 'o', 'v', 'p', 'h', '^', '>', '<', '.'] 437 | 438 | cluster_dfs, _ = self.extract_clusters() 439 | if with_noise is False: 440 | cluster_dfs.pop(-1) 441 | 442 | if cluster_dfs is None: 443 | print("[!] No clusters to plot. Exiting.") 444 | return 445 | 446 | 447 | for cluster_id in sorted(cluster_dfs.keys()): 448 | if cluster_id == -1: 449 | color = "black" 450 | else: 451 | color = colors[cluster_id] 452 | 453 | plt.scatter(cluster_dfs[cluster_id][feature_1], 454 | cluster_dfs[cluster_id][feature_2], 455 | edgecolors="k", 456 | marker=shapes[cluster_id], 457 | color=color, 458 | alpha=1) 459 | 460 | plt.xlabel(feature_1) 461 | plt.ylabel(feature_2) 462 | plt.title(self.pathname.split('/')[-1:][0] + " clusters") 463 | plt.show() 464 | 465 | 466 | 467 | 468 | def extract_clusters(self): 469 | cluster_dataframes = {} # key = cluster ID, value = that cluster's data frame 470 | cluster_bytes = {} # key = cluster ID, value = list of all bytes in cluster 471 | 472 | if self.dbscan_data_frame is not None: 473 | cluster_labels = list(set(self.dbscan_data_frame["cluster labels"])) # example output: [0, 1, 2, -1] 474 | for label in cluster_labels: 475 | 476 | # extract data frame 477 | cluster_df = self.dbscan_data_frame[self.dbscan_data_frame["cluster labels"] == label] 478 | cluster_dataframes[label] = cluster_df 479 | 480 | # extract data/bytes of all blocks in cluster 481 | bytes = [] 482 | blocks = [self.blocks[i] for i in cluster_df.index] 483 | 484 | for block in blocks: 485 | bytes += block.data 486 | 487 | cluster_bytes[label] = bytes 488 | 489 | else: 490 | print("[!] No cluster data frames to extract\n") 491 | return None, None 492 | 493 | return cluster_dataframes, cluster_bytes 494 | 495 | 496 | 497 | 498 | def load_data_type_distributions(self): 499 | distributions = {} 500 | base_directory = os.path.dirname(__file__) 501 | load_path = base_directory + "/distributions/data_types/" 502 | 503 | for file in os.listdir(load_path): 504 | if os.path.isdir(load_path + file): 505 | continue 506 | with open(load_path + file, "rb") as f: 507 | try: 508 | distributions[file] = pickle.load(f) 509 | except: 510 | continue 511 | 512 | return distributions 513 | 514 | 515 | 516 | def load_machine_code_distributions(self): 517 | distributions = {} 518 | base_directory = os.path.dirname(__file__) 519 | load_path = base_directory + "/distributions/cpu_architectures/" 520 | 521 | for file in os.listdir(load_path): 522 | if os.path.isdir(load_path + file): 523 | continue 524 | with open(load_path + file, "rb") as f: 525 | try: 526 | distributions[file] = pickle.load(f) 527 | except: 528 | continue 529 | 530 | return distributions 531 | 532 | 533 | 534 | def id_code_clusters(self, cluster_dfs, cluster_bytes, reference_dist): 535 | 536 | distances = {} # store initial distance measurements between clusters and data type distributions 537 | closely_matching_arch_ref = False 538 | arch_classification = None 539 | for id, bytes in cluster_bytes.items(): 540 | id_string = "Cluster " + str(id) 541 | initial_d = wasserstein_distance(reference_dist, bytes) 542 | distances[id_string] = initial_d 543 | in_code_range = False 544 | if (cluster_dfs[id]["entropy"].mean() > 5.2 and cluster_dfs[id]["entropy"].mean() < 6.8): # Initial cutoff. 545 | in_code_range = True 546 | arch_distances = {} # store distance measurements between a cluster and each CPU arch. ref. dist. 547 | mc_reference_distributions = self.load_machine_code_distributions() 548 | print("[+] Checking %s for possible match" % id_string) 549 | for arch, ref_bytes in mc_reference_distributions.items(): 550 | code_d = wasserstein_distance(ref_bytes, bytes) 551 | arch_distances[arch] = code_d 552 | if code_d <= 10: # second cutoff. Looking for close matches 553 | closely_matching_arch_ref = True 554 | 555 | if closely_matching_arch_ref is True: 556 | print("[+] Closely matching CPU architecture reference(s) found for %s" % id_string) 557 | arch_classification = self.get_arch_ID(bytes) 558 | else: 559 | if in_code_range is True: 560 | print("[X] No closely matching CPU architecture reference found.\n\n") 561 | 562 | distances[id_string] = [distances[id_string], arch_distances] 563 | closely_matching_arch_ref = False 564 | 565 | if arch_classification is None: 566 | print("[X] No machine code cluster detected\n\n") 567 | 568 | #distances = json.dumps(distances, indent = 4) 569 | return distances, arch_classification 570 | 571 | 572 | 573 | 574 | def id_utf8_en_clusters(self, cluster_bytes, reference_dist): 575 | distances = {} 576 | match_found = False 577 | for id, bytes in cluster_bytes.items(): 578 | id_string = "Cluster " + str(id) 579 | d = wasserstein_distance(reference_dist, bytes) 580 | distances[id_string] = d 581 | if d < 30: # initial cutoff 582 | print("[+] UTF-8 (english) detected in %s\n Wasserstein distance to reference: %s\n" % (id_string, d)) 583 | match_found = True 584 | 585 | if match_found is False: 586 | print("[X] No UTF-8 (english) cluster detected.\n") 587 | 588 | #distances = json.dumps(distances, indent = 4) 589 | #print(distances) 590 | return distances 591 | 592 | 593 | 594 | def id_high_entropy_clusters(self, cluster_dfs, cluster_bytes, reference_dist): 595 | distances = {} 596 | match_found = False 597 | for id, bytes in cluster_bytes.items(): 598 | id_string = "Cluster " + str(id) 599 | d = wasserstein_distance(reference_dist, bytes) 600 | distances[id_string] = d 601 | if d < 10: # initial cutoff 602 | print("[+] High entropy data found in %s\n Wasserstein distance to reference: %s" % (id_string, d)) 603 | match_found = True 604 | if d < 1: 605 | print("[*] This distance suggests the data in this cluster could be\n" \ 606 | " a) encrypted\n" \ 607 | " b) compressed via LZMA with maximum compression level\n" \ 608 | " c) something else that is random or close to random.") 609 | else: 610 | print("[*] This distance suggests the data in this cluster is compressed\n") 611 | 612 | if match_found is False: 613 | print("[X] No high entropy data cluster detected.\n") 614 | 615 | #distances = json.dumps(distances, indent = 4) 616 | #print(distances) 617 | return distances 618 | 619 | 620 | 621 | 622 | def identify_cluster_data_types(self, show_all=False): 623 | cluster_dfs, cluster_bytes = self.extract_clusters() 624 | cluster_bytes.pop(-1, None) # get rid of noise 625 | reference_distributions = self.load_data_type_distributions() 626 | 627 | print("Searching for machine code\n--------------------------------------------------------------------\n") 628 | code_distances, arch_classification = self.id_code_clusters(cluster_dfs, cluster_bytes, reference_distributions["machine_code"]) 629 | 630 | print("\nSearching for utf8-english data\n-------------------------------------------------------------------\n") 631 | utf8_en_distances = self.id_utf8_en_clusters(cluster_bytes, reference_distributions["utf8_english"]) 632 | 633 | print("\nSearching for high entropy data\n-------------------------------------------------------------------\n") 634 | high_entropy_distances = self.id_high_entropy_clusters(cluster_dfs, cluster_bytes, reference_distributions["max_entropy"]) 635 | 636 | full_results = {"machine code": [code_distances, arch_classification], 637 | "utf8_en": utf8_en_distances, 638 | "high entropy": high_entropy_distances} 639 | 640 | if show_all is True: 641 | print("\n\nFull results: \n") 642 | print(json.dumps(full_results, indent=4)) 643 | 644 | return full_results 645 | 646 | 647 | 648 | 649 | def get_arch_ID(self, data): 650 | print("[+] Sending sample to https://isadetect.com/") 651 | req = requests.post("https://isadetect.com/binary/", 652 | files = { "binary":bytes(data) }, 653 | data = {"type": "code"}) 654 | print("[+] response:\n") 655 | response = json.dumps(req.json(), indent=4, sort_keys=True) 656 | print(response + "\n") 657 | 658 | return req.json() 659 | 660 | 661 | 662 | def plot_cluster_cdfs(self): 663 | #colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', 664 | # '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'] 665 | colors = ['tab:blue', 'tab:orange', 'tab:green', 'tab:red', 'tab:purple', 666 | 'tab:brown', 'tab:pink', 'tab:gray', 'tab:olive', 'tab:cyan'] 667 | counter = 0 668 | 669 | _, cluster_bytes = self.extract_clusters() 670 | cluster_bytes.pop(-1, None) 671 | for cluster_id, bytes in cluster_bytes.items(): 672 | sns.distplot(bytes, 673 | norm_hist=True, 674 | kde=False, 675 | hist_kws={'histtype':'step', 'cumulative': True, 'linewidth':2, 'alpha':1}, 676 | kde_kws={'cumulative': True}, 677 | bins=256, 678 | color=colors[counter % len(colors)]) # wrap around 679 | counter += 1 680 | plt.title("CDF of Cluster %d" % cluster_id) 681 | plt.xlim(-10, 265) 682 | plt.show() 683 | 684 | 685 | 686 | def plot_cluster_histograms(self): 687 | colors = ['tab:blue', 'tab:orange', 'tab:green', 'tab:red', 'tab:purple', 688 | 'tab:brown', 'tab:pink', 'tab:gray', 'tab:olive', 'tab:cyan'] 689 | counter = 0 690 | 691 | _, cluster_bytes = self.extract_clusters() 692 | cluster_bytes.pop(-1, None) 693 | for cluster_id, bytes in cluster_bytes.items(): 694 | sns.distplot(bytes, 695 | kde=False, 696 | hist_kws={'alpha':1}, 697 | bins=256, 698 | color=colors[counter % len(colors)]) # wrap around 699 | counter += 1 700 | plt.title("Byte Value Histogram of Cluster %d" % cluster_id) 701 | plt.xlim(-10, 265) 702 | plt.show() 703 | 704 | 705 | 706 | def cluster_scatterplot_matrix(self): 707 | sns.pairplot(self.dbscan_data_frame[self.dbscan_data_frame["cluster labels"] != -1].drop(["core samples mask"], axis=1), hue="cluster labels") 708 | 709 | 710 | def violinplot_cluster_by_feature(self, feature): 711 | if feature not in self.dbscan_data_frame.columns: 712 | print("[!] feature must be one of the following:\n") 713 | for column in self.dbscan_data_frame.drop(["core samples mask", "cluster labels"], axis=1).columns: 714 | print(column) 715 | return 716 | 717 | with sns.axes_style("whitegrid"): 718 | sns.color_palette("rainbow") 719 | sns.violinplot(x = "cluster labels", 720 | y = feature, 721 | data = self.dbscan_data_frame[self.dbscan_data_frame["cluster labels"] != -1]) 722 | plt.title(self.pathname.split('/')[-1:][0] + " clusters") 723 | plt.show() 724 | 725 | 726 | 727 | def boxplot_cluster_by_feature(self, feature): 728 | if feature not in self.dbscan_data_frame.columns: 729 | print("[!] feature must be one of the following:\n") 730 | for column in self.dbscan_data_frame.drop(["core samples mask", "cluster labels"], axis=1).columns: 731 | print(column) 732 | return 733 | 734 | with sns.axes_style("whitegrid"): 735 | sns.boxplot(x = "cluster labels", 736 | y = feature, 737 | data = self.dbscan_data_frame[self.dbscan_data_frame["cluster labels"] != -1]) 738 | plt.title(self.pathname.split('/')[-1:][0] + " clusters") 739 | plt.show() 740 | 741 | -------------------------------------------------------------------------------- /centrifuge/datablock.py: -------------------------------------------------------------------------------- 1 | 2 | import matplotlib.pyplot as plt 3 | import seaborn as sns; sns.set() 4 | import numpy as np 5 | from scipy.stats import entropy 6 | 7 | 8 | def to_byte_dict(data): 9 | byte_dict = {} 10 | for i in range(0, 256): byte_dict.update({i:0}) 11 | for i in list(data): byte_dict[i]+= 1 12 | return byte_dict 13 | 14 | 15 | def count_ascii(byte_dict): 16 | num_ascii = 0 17 | for i in range(0, 256): 18 | if byte_dict[i] >= 32 and byte_dict[i] <= 126: 19 | num_ascii += byte_dict[i] 20 | 21 | return num_ascii 22 | 23 | 24 | class DataBlock: 25 | def __init__(self, pathname, data_block, block_size, file_offset): 26 | # self explanatory 27 | self.path = pathname 28 | self.data = data_block 29 | self.size = block_size 30 | self.offset = file_offset 31 | 32 | # really annoying. Have to do this if we want to use an externally declared function 33 | # there must be a better way to do this 34 | self.to_byte_dict = to_byte_dict 35 | self.byte_dict = to_byte_dict(self.data) 36 | 37 | # features engineered for this data 38 | self.entropy = entropy(list(self.byte_dict.values()), base=2) # entropy 39 | 40 | self.zeroes_ratio = self.byte_dict[0] / block_size # percent of bytes that are 0 41 | self.ascii_ratio = count_ascii(self.byte_dict) / block_size # percent of bytes that fall within the ASCII range 42 | 43 | self.byteval_std_dev_counts = np.std(list(self.byte_dict.values())) 44 | self.byteval_std_dev = np.std(list(data_block)) 45 | 46 | self.byteval_mean = np.mean(list(data_block)) 47 | self.byteval_median = np.median(list(data_block)) 48 | 49 | # methods 50 | 51 | def plot_relative_frequency_distribution(self): 52 | 53 | # unvariate 54 | #plt.rcParams['figure.figsize'] = [15, 5] 55 | ax = sns.distplot(np.array(list(self.data)), bins=256, kde=False, norm_hist=True, color='purple'); 56 | ax.set(xlabel='Byte Value (base 10)', 57 | ylabel='Frequency', 58 | title='Byte Value Distribution at offset ' + str(self.offset) + ' in ' + self.path) 59 | # control x axis range 60 | ax.set_xlim(-10, 260) 61 | #ax.set_ylim(0, 0.10) 62 | plt.show() 63 | 64 | 65 | def plot_cdf(self): 66 | #plt.rcParams['figure.figsize'] = [15, 5] 67 | ax = sns.distplot(np.array(list(self.data)), 68 | bins=256, 69 | kde=False, 70 | hist_kws={'histtype':'step', 'cumulative': True, 'linewidth':1, 'alpha':1}, 71 | kde_kws={'cumulative': True}, 72 | norm_hist=True, 73 | color='red'); 74 | ax.set(xlabel='Byte Value (base 10)', 75 | ylabel='Probability', 76 | title='CDF of byte values at offset ' + str(self.offset) + ' in ' + self.path) 77 | # control x axis range 78 | ax.set_xlim(-10, 260) 79 | #ax.set_ylim(0, 0.10) 80 | 81 | plt.show() 82 | 83 | -------------------------------------------------------------------------------- /centrifuge/distributions/cpu_architectures/AMD64_reference: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/distributions/cpu_architectures/AMD64_reference -------------------------------------------------------------------------------- /centrifuge/distributions/cpu_architectures/ARM64_reference: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/distributions/cpu_architectures/ARM64_reference -------------------------------------------------------------------------------- /centrifuge/distributions/cpu_architectures/ARMEL_reference: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/distributions/cpu_architectures/ARMEL_reference -------------------------------------------------------------------------------- /centrifuge/distributions/cpu_architectures/MIPS64EL_reference: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/distributions/cpu_architectures/MIPS64EL_reference -------------------------------------------------------------------------------- /centrifuge/distributions/cpu_architectures/MIPSEL_reference: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/distributions/cpu_architectures/MIPSEL_reference -------------------------------------------------------------------------------- /centrifuge/distributions/cpu_architectures/PPC64_reference: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/distributions/cpu_architectures/PPC64_reference -------------------------------------------------------------------------------- /centrifuge/distributions/cpu_architectures/PowerPC_reference: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/distributions/cpu_architectures/PowerPC_reference -------------------------------------------------------------------------------- /centrifuge/distributions/cpu_architectures/SH4_reference: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/distributions/cpu_architectures/SH4_reference -------------------------------------------------------------------------------- /centrifuge/distributions/cpu_architectures/i386_reference: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/distributions/cpu_architectures/i386_reference -------------------------------------------------------------------------------- /centrifuge/distributions/data_types/archive/readme.txt: -------------------------------------------------------------------------------- 1 | Examples of data types are machine code, ASCII text, compressed data, encrypted data, ELF debug info, etc. 2 | After DBSCAN performs clustering, it will compare the byte distribution of each cluster to a data type reference. 3 | This way no non-machine code information is compared with the reference distributions of the CPU architectures. 4 | In other words, only if the cluster is identified to be data type "code" will an attempt to identify the 5 | architecture be made. 6 | 7 | 8 | 9 | To create the machine code reference, take a sample of size N bytes from all the architectures, merge them, then 10 | build. 11 | 12 | 13 | -------------------------------------------------------------------------------- /centrifuge/distributions/data_types/machine_code: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/distributions/data_types/machine_code -------------------------------------------------------------------------------- /centrifuge/distributions/data_types/max_entropy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/distributions/data_types/max_entropy -------------------------------------------------------------------------------- /centrifuge/distributions/data_types/utf8_english: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/centrifuge/distributions/data_types/utf8_english -------------------------------------------------------------------------------- /centrifuge/utils/plotutils.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def plot_file_entropy(bf, start=None, end=None): 4 | '''bf argument is an instance of the BinFile class, start and none are numbers representing offsets within the file''' 5 | 6 | try: 7 | plt.axvline(x=start, color='red') 8 | plt.axvline(x=end, color='red') 9 | except TypeError: 10 | pass 11 | 12 | plt.plot(bf.block_offsets, 13 | bf.block_entropy_levels, 14 | linewidth=0.8, 15 | color='blue') 16 | 17 | plt.title("Entropy of " + str(bf.pathname)) 18 | plt.xlabel('Offset') 19 | plt.ylabel('Entropy') 20 | plt.ylim(-0.5, 8) 21 | 22 | plt.show() 23 | 24 | 25 | 26 | def quickplot(x, y, c, title, xl, yl): 27 | 28 | plt.scatter(x, y, alpha=0.15, color=c) 29 | 30 | plt.title(title) 31 | plt.xlabel(xl) 32 | plt.ylabel(yl) 33 | #plt.xlim(-0.05, 1) 34 | #plt.ylim(0, 8.5) 35 | 36 | plt.show() 37 | -------------------------------------------------------------------------------- /gallery/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/1.png -------------------------------------------------------------------------------- /gallery/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/10.png -------------------------------------------------------------------------------- /gallery/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/11.png -------------------------------------------------------------------------------- /gallery/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/12.png -------------------------------------------------------------------------------- /gallery/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/13.png -------------------------------------------------------------------------------- /gallery/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/14.png -------------------------------------------------------------------------------- /gallery/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/15.png -------------------------------------------------------------------------------- /gallery/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/16.png -------------------------------------------------------------------------------- /gallery/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/17.png -------------------------------------------------------------------------------- /gallery/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/18.png -------------------------------------------------------------------------------- /gallery/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/19.png -------------------------------------------------------------------------------- /gallery/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/2.png -------------------------------------------------------------------------------- /gallery/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/3.png -------------------------------------------------------------------------------- /gallery/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/4.png -------------------------------------------------------------------------------- /gallery/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/5.png -------------------------------------------------------------------------------- /gallery/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/6.png -------------------------------------------------------------------------------- /gallery/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/7.png -------------------------------------------------------------------------------- /gallery/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/8.png -------------------------------------------------------------------------------- /gallery/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/gallery/9.png -------------------------------------------------------------------------------- /images/approach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/images/approach.png -------------------------------------------------------------------------------- /images/approach_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/images/approach_2.png -------------------------------------------------------------------------------- /images/approach_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinaryResearch/centrifuge-toolkit/d68a0ba7df8ab2ef9cd8cd34a1dfa8183a641285/images/approach_3.png -------------------------------------------------------------------------------- /notebooks/Data Type Reference Distributions/The Machine Code Reference Distribution.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 62, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import os\n", 10 | "import pickle\n", 11 | "import pandas as pd\n", 12 | "import matplotlib.pyplot as plt\n", 13 | "import seaborn as sns\n", 14 | "plt.rcParams['figure.figsize'] = [16, 9]\n", 15 | "sns.set_style(\"whitegrid\")\n", 16 | "\n", 17 | "from scipy import stats" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 3, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "path = \"../centrifuge/distributions/cpu_architectures/\"\n", 27 | "archs = os.listdir(path)\n", 28 | "\n", 29 | "reference_dict = {}\n", 30 | "\n", 31 | "for file in archs:\n", 32 | " with open(path + file, \"rb\") as f:\n", 33 | " reference_dict[file] = pickle.load(f)" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 4, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "reference_df = pd.DataFrame(index = reference_dict.keys(),\n", 43 | " columns = [i for i in range(1000)])\n", 44 | "\n", 45 | "for file, code in reference_dict.items():\n", 46 | " reference_df.loc[file] = code" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 63, 52 | "metadata": {}, 53 | "outputs": [ 54 | { 55 | "data": { 56 | "image/png": "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998vSfr617+uxsZG7d27l70jAQDIBT4OISJ4OuPHek+joLXfGsPnsGHD2l44GkSPb+kUOVRywrHxFzC02j417kI95fBzwwFz9dFBREbDqzc7vAoAyUEQHThwoOrr6/Xxxx+roqJCq1at0rx586LO6dmzp1599VVddtll2rx5sxobGyPTYgEAQBZKU/i0GjxENd4e4fPFyGNj+Kw5uuazNXy2BtC48Ll+fXS1M6bVNtYD32+bqrtx0sZI8IwNoIRPIHm2QbSoqEi33HKLJk+erObmZo0ZM0Ynn3yy5s+frwEDBmj48OG64YYb9Mtf/lIPPPCACgoKNGfOnLj2XbfS9Ztf0H5TBQAga3gYPuGMny23UvD+nhQOn8OGDYuufB4VCoV06L4/mr533kltM07sgqfkPHwC8IajfUSHDh0aty/otGnTIo/79++vP/7R/DeBbPTcc8+ptrZWjz/+uE477TRt27ZN3//+99W3b181NTVpwIABmjVrloqLi/X666/riiuu0K233qqxY8dKkt59911deumlmjFjhn784x9Lkh588EEtW7ZMRUVFGjp0qGbMmBH5vO3bt2v06NGRoU+pWr9+vX71q1+pqKhIy5cvV/v27VO+JgAgD6Sw/Uosu/BJC661fAufxkqnUcLw2fNUSYoKoXsa25aS6UNJxiBqMH35yqipt2FOwicVUMA7joJoJnn1m6Ob66xcuVJnnHGGnnnmGZ122mmSpK985St64okn1NzcrH/+53/W008/rYsvvliS9NWvflVPP/10JIiuWrVKp556auR6r732mlavXq0VK1aopKREu3fvjvq82bNna8iQIY7vr6WlRS0tLZbDoJ588kldddVVGjNmjCfXAwDkMAfDh7xC+LTmd/jMtETh04zTv8f94fxz447ZDQ4K7/1J+AT8Ffgg6rcvv/xS//u//6v/+I//0E9+8hNdd911Ua+3a9dOgwYN0s6dOyPHKisr9cUXX+jvf/+7unbtqpdffjmqgvzwww+rpqZGJSUlkhQ1xOn5559Xr1691LFjx4T3tW3bNk2ZMkVnnnmm3n77bd11113asmWLFixYoEOHDql3796aPXu2nnrqKT3zzDNau3atXnnlFc2bN0/333+/nn76aR06dEjnn3++rrnmGsfXO+aYY1RdXa1LLrlEL7zwgg4fPqw77rhDJ510kr788kvdeuut2rRpk6TWbXxGjRqltWvXml4HABAAToKnx+s/0crJGk+jdIbPTFRB7cJnKBTSji7HmZ6z48471KU0vsJprIL26Nc/Yeg0Bs07eie+V8InkH4E0RjPP/+8hgwZor59+6pTp076y1/+os6d2/7Qbmxs1DvvvBO3z+eoUaP0zDPPqKqqSqeffnokdEpSfX291q9fr9/97ncqLS3VjBkzNGjQIB04cEC///3vtXjxYi1evNj23rZs2aLZs2crFAppz549uueee7RkyRJ17NhRixYt0pIlS1RbW6s333xTw4YN0wUXXKC1a9dq69atevTRR9XS0qKrr75ab7zxhnr27On4epJUXl6uxx9/XH/4wx+0ePFizZo1S3fffbeOPfbYyFYzn3/+ue11AAAZkMbwCW+lK3xmqv3WKnyGt1kpPHhA2vC2pNaw6VaPfv2jJuCmsp6T8An4iyAaY9WqVZo0aZIk6YILLtDKlSv1wx/+UB999JH+6Z/+SVu3btWoUaOiWm8l6Xvf+55+/vOf68MPP9To0aP11ltvRV5rbm7Wvn379Mgjj2jjxo269tprtXr1ai1YsECTJk1yXC2srKzU4MGDJUnvvPOOPvjgA40fP16S1NTUFHnNaN26dVq3bp0uueQSSdKBAwdUX1+vnj17urreyJEjJUkDBgzQc889J0l69dVX9dvf/jZyTufOnfXCCy84ui8AQJr5sOWK1TpQqqBtzKqgfrXZZnrtZ6yo8GmjR7/+kqRDn3wR91rsRFy34TM2cDKxOXl1p/LrhuQRRA327t2r1157Te+//74KCgp0+PBhFRYWasKECZE1op999pkmTpyo1atXa/jw4ZH3duvWTUVFRVq3bp1uuummqCBaUVGh888/XwUFBRo0aJAKCwu1d+9evfPOO3r22Wc1d+5c7du3T4WFhSotLdWPfvQj0/sztu+2tLTonHPOiQqCZlpaWlRTU6Mf/OAHUce3bdvm6nrFxcWSpMLCQjU3N0feEzsd2el9AQDSwMf9PmHNbQuul4IQPscbtvkbf/SfBYebVNghfnhiOHCaWr9en93wctzhXnOG2IbPaz9u+zvOxkkbE98wgIwIfBD18zfUZ599VpdccolmzpwpSWpoaNCUKVOi1oN2795d119/vRYtWhQVRCXpmmuu0Z49e9SuXbuo4yNGjNBrr72mM888U1u2bFFT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ztBbb72lqqoqjRkzRnfeeaf27NmjuXPnqn///vr1r3+t9957T83NzaqtrdWIESO0bds2zZgxQw0NDZKkm2++Wd/4xjf0+uuva+HChSovL9d7772n008/XXPnzlVBQUGGf1oAQL6xqoAm047r5dTbp0L/qqdMjjsZMFR08LjIY6fhM50VTrPwGQqFImHzsaWPub4mgRNeoQUXQUEQNfHRRx9p/vz5+sUvfqGJEydqxYoVevjhh7V69Wrde++96t+/v8466yzNnj1b+/bt09ixY3X22Wera9euWrJkiUpLS1VfX6/rrrtOf/7znyVJ7777rlatWqXu3btr/PjxevPNN/XNb37T5k4AADkh1DmjH+9l+62X4TOV6bbG8CnJNHz62U5rVwV1WvEkcCId7MInVVBkAkHURK9evXTKKaeooaFB/fv313e+8x0VFBTolFNO0SeffKIdO3ZozZo1Wrx4sSSpsbFRn376qbp3766ZM2fqr3/9qwoLC1VfXx+55qBBg9SjRw9J0qmnnqpPPvmEIAoAyBg3FVAnU2+dsgqfl61+pu2xxXutwmenEX3UyfEdpObut++WJFVcWhF13FgHPdV0AAAgAElEQVTlHKMxrcf6mlc+CZvwA+ETQUcQNVFSUhJ5XFhYGHleUFCg5uZmtWvXTnfeeaf69esX9b4FCxbo+OOP1xNPPKEjR45o0KBBptds166dmpvtJ/oBAHJM6HPfPsqsCmoXPt2s8Uw2fBoHDDlhDJ/tOpVEba3iZfgMB8xEKi6tUEXfCtPXxmwZE3eMwAm/ET6RTQiiSTj33HO1bNky3XzzzSooKNC7776r0047Tfv371ePHj1UWFioxx9/nLAJAPBVuibghrkJn4d6nhp5PKXTlgRnRoubdFsSva9nqpwETqPYymfYxkkbPdmGBXDL7RpPwieCiiCahKlTp+q2227TxRdfrJaWFp1wwgm67777NGHCBP3sZz/TM888ozPPPFMdO3bM9K0CAHKcXfhMpgqa7BrPn730QcLzjVXQ2MDZdPiwiota/1riZfCU3IXPqYOnphQwCadIB8InchFBNEavXr20cuXKyPM5c+aYvjZz5sy495544olasWJF5Pn06dMlSWeeeabOPPPMyPFbbrnF8/sGACAsUfhMdYsVY/hMFDwPHq2C7t7dHNlnJTZ8GgPnrl271K1bt6Tvy22l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\n", 57 | "text/plain": [ 58 | "
" 59 | ] 60 | }, 61 | "metadata": {}, 62 | "output_type": "display_data" 63 | } 64 | ], 65 | "source": [ 66 | "for code in reference_dict.values():\n", 67 | " sns.distplot(code,\n", 68 | " norm_hist=True, \n", 69 | " kde=False,\n", 70 | " hist_kws={'histtype':'step', 'cumulative': True, 'linewidth':2, 'alpha':1},\n", 71 | " kde_kws={'cumulative': True}, \n", 72 | " bins=256)\n", 73 | "\n", 74 | "sns.distplot(reference_df.mean(axis = 0), \n", 75 | " norm_hist=True, \n", 76 | " kde=False, \n", 77 | " hist_kws={'histtype':'step', 'cumulative': True, 'linewidth':10, 'alpha':0.5},\n", 78 | " kde_kws={'cumulative': True}, \n", 79 | " bins=256,\n", 80 | " color=\"black\")\n", 81 | "\n", 82 | "sns.distplot(reference_df.median(axis = 0), \n", 83 | " norm_hist=True, \n", 84 | " kde=False, \n", 85 | " hist_kws={'histtype':'step', 'cumulative': True, 'linewidth':3, 'alpha':0.8},\n", 86 | " kde_kws={'cumulative': True}, \n", 87 | " bins=256,\n", 88 | " color=\"red\")\n", 89 | "\n", 90 | "plt.legend(list(reference_dict.keys())+[\"mean\"]+[\"median\"], loc=\"upper left\")\n", 91 | "plt.title(\"Mean of reference distributions vs. All reference distributions\")\n", 92 | "plt.show()" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": 7, 98 | "metadata": {}, 99 | "outputs": [ 100 | { 101 | "data": { 102 | "text/html": [ 103 | "
\n", 104 | "\n", 117 | "\n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | "
Wasserstein Distance (Mean)Wasserstein Distance (Median)
PPC64_reference5.254332.46349
SH4_reference6.86794.77315
ARM64_reference6.969694.47539
AMD64_reference8.021075.39967
i386_reference11.33169.2355
PowerPC_reference12.470814.5362
MIPS64EL_reference12.896714.7124
ARMEL_reference17.38716.1934
MIPSEL_reference23.809925.8836
\n", 173 | "
" 174 | ], 175 | "text/plain": [ 176 | " Wasserstein Distance (Mean) Wasserstein Distance (Median)\n", 177 | "PPC64_reference 5.25433 2.46349\n", 178 | "SH4_reference 6.8679 4.77315\n", 179 | "ARM64_reference 6.96969 4.47539\n", 180 | "AMD64_reference 8.02107 5.39967\n", 181 | "i386_reference 11.3316 9.2355\n", 182 | "PowerPC_reference 12.4708 14.5362\n", 183 | "MIPS64EL_reference 12.8967 14.7124\n", 184 | "ARMEL_reference 17.387 16.1934\n", 185 | "MIPSEL_reference 23.8099 25.8836" 186 | ] 187 | }, 188 | "execution_count": 7, 189 | "metadata": {}, 190 | "output_type": "execute_result" 191 | } 192 | ], 193 | "source": [ 194 | "distance_df = pd.DataFrame(index = reference_dict.keys(),\n", 195 | " columns = [\"Wasserstein Distance (Mean)\",\n", 196 | " \"Wasserstein Distance (Median)\"])\n", 197 | "\n", 198 | "for file, code in reference_dict.items():\n", 199 | " distance_df.loc[file][\"Wasserstein Distance (Mean)\"] = stats.wasserstein_distance(reference_df.mean(axis = 0), code)\n", 200 | " distance_df.loc[file][\"Wasserstein Distance (Median)\"] = stats.wasserstein_distance(reference_df.median(axis = 0), code)\n", 201 | "\n", 202 | "distance_df.sort_values(\"Wasserstein Distance (Mean)\")" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": null, 208 | "metadata": {}, 209 | "outputs": [], 210 | "source": [ 211 | "distance_df.mean(axis=0)" 212 | ] 213 | }, 214 | { 215 | "cell_type": "code", 216 | "execution_count": null, 217 | "metadata": {}, 218 | "outputs": [], 219 | "source": [ 220 | "with open(\"../distributions/data_types/machine_code\", \"wb\") as f:\n", 221 | " pickle.dump(reference_df.mean(axis=0), f)" 222 | ] 223 | }, 224 | { 225 | "cell_type": "markdown", 226 | "metadata": {}, 227 | "source": [ 228 | "Though using the medians minimizes distance overall, using the means might be a better choice. The more distant distributions (MIPSEL, MIPS64EL, ARMEL, PowerPC) are less distant to the means.\n", 229 | "\n", 230 | "
\n", 231 | "\n", 232 | "Since the above distributions are so diverse, an entropy cutoff was used to identify clusters containing code, rather than using Wasserstein distance between the distribution of byte values in the cluster and this reference distribution. Currently, this metric is still measured, but not used." 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": 33, 238 | "metadata": {}, 239 | "outputs": [], 240 | "source": [ 241 | "def plot_dist_cdf(df):\n", 242 | " for entry in list(df.index):\n", 243 | " sns.distplot(df.loc[entry],\n", 244 | " norm_hist=True, \n", 245 | " kde=False,\n", 246 | " hist_kws={'histtype':'step', 'cumulative': True, 'linewidth':2, 'alpha':1},\n", 247 | " kde_kws={'cumulative': True}, \n", 248 | " bins=256)\n", 249 | " plt.legend(list(df.index), loc=\"upper left\")\n", 250 | " plt.show()" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": 46, 256 | "metadata": {}, 257 | "outputs": [], 258 | "source": [ 259 | "def create_family_ref(mc_ref_dist, dist_1, dist_2):\n", 260 | " family_df = pd.DataFrame(index=[dist_1, dist_2],\n", 261 | " columns=[i for i in range(0,1000)])\n", 262 | " family_df.loc[dist_1] = mc_ref_dist.loc[dist_1]\n", 263 | " family_df.loc[dist_2] = mc_ref_dist.loc[dist_2]\n", 264 | " family_df_median = family_df.median(axis = 0)\n", 265 | " family_df = family_df.append(pd.Series(family_df_median, name=\"medians\"))\n", 266 | " \n", 267 | " return family_df, family_df_median" 268 | ] 269 | } 270 | ], 271 | "metadata": { 272 | "kernelspec": { 273 | "display_name": "Python 3", 274 | "language": "python", 275 | "name": "python3" 276 | }, 277 | "language_info": { 278 | "codemirror_mode": { 279 | "name": "ipython", 280 | "version": 3 281 | }, 282 | "file_extension": ".py", 283 | "mimetype": "text/x-python", 284 | "name": "python", 285 | "nbconvert_exporter": "python", 286 | "pygments_lexer": "ipython3", 287 | "version": "3.7.6" 288 | } 289 | }, 290 | "nbformat": 4, 291 | "nbformat_minor": 2 292 | } 293 | -------------------------------------------------------------------------------- /notebooks/Data Type Reference Distributions/The UTF-8 (English) Reference Distribution.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 8, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import os\n", 10 | "import pickle\n", 11 | "import numpy as np\n", 12 | "import pandas as pd\n", 13 | "import matplotlib.pyplot as plt\n", 14 | "import seaborn as sns\n", 15 | "from tqdm import tqdm\n", 16 | "from scipy import stats\n", 17 | "\n", 18 | "plt.rcParams['figure.figsize'] = [16, 9]\n", 19 | "sns.set_style(\"whitegrid\")" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 2, 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "data": { 29 | "text/plain": [ 30 | "['62786-0.txt', '62819.txt', '62875-0.txt']" 31 | ] 32 | }, 33 | "execution_count": 2, 34 | "metadata": {}, 35 | "output_type": "execute_result" 36 | } 37 | ], 38 | "source": [ 39 | "path = \"../files/utf8-english/\"\n", 40 | "files = os.listdir(path)\n", 41 | "files.remove('README.txt')\n", 42 | "files" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 3, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "text_data = {}\n", 52 | "\n", 53 | "for file in files:\n", 54 | " with open(path + file, \"rb\") as f:\n", 55 | " f.seek(10000)\n", 56 | " text_data[file] = list(f.read(50000))" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": 4, 62 | "metadata": {}, 63 | "outputs": [ 64 | { 65 | "name": "stderr", 66 | "output_type": "stream", 67 | "text": [ 68 | "100%|██████████| 1000/1000 [00:47<00:00, 20.97it/s]\n", 69 | "100%|██████████| 1000/1000 [00:48<00:00, 20.77it/s]\n", 70 | "100%|██████████| 1000/1000 [00:48<00:00, 20.69it/s]\n" 71 | ] 72 | } 73 | ], 74 | "source": [ 75 | "file_means = {}\n", 76 | "file_medians = {}\n", 77 | "\n", 78 | "for file, text in text_data.items():\n", 79 | " df = pd.DataFrame(index=[i for i in range(0,1000)],\n", 80 | " columns=[i for i in range(0,1000)])\n", 81 | " for i in tqdm(range(0, 1000)):\n", 82 | " df.iloc[i] = sorted(np.random.choice(text, size=1000, replace=True))\n", 83 | " \n", 84 | " file_means[file] = df.mean(axis = 0)\n", 85 | " file_medians[file] = df.median(axis = 0)" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 22, 91 | "metadata": {}, 92 | "outputs": [ 93 | { 94 | "data": { 95 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAA6IAAAIICAYAAAB0CFO7AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAgAElEQVR4nO3df3SddZ0n8Hf6kxZKlWobtpaWX2IOxHYZcWUEO7Z2kEmxWGBGZLGyMHg8MyKKYCs/rB1LcwbZocye6VAYsAcQZEBaacYOGMVyHARmj9o4DbDIZLe4Nswiv+3vZv+ovZImbdI2ee69yet1Tk/u89wn935u+s1N3vl8n+9T09HR0REAAAAoyJByFwAAAMDgIogCAABQKEEUAACAQgmiAAAAFEoQBQAAoFCCKAAAAIUaVq4n/tnPfpaRI0cmSbZs2VK6DQOd8c5gYrwzmBjvDCbGO72xZcuWTJs2rdv7yhZER44cmbq6uiRJa2tr6TYMdMY7g4nxzmBivDOYGO/0Rmtr617vMzUXAACAQgmiAAAAFEoQBQAAoFBlO0e0O9u2bcsLL7yQzZs3l7uUQeWQQw7Ju971rgwfPrzcpQAAAINARQXRF154IWPGjMmUKVNSU1NT7nIGhY6Ojrz00kt54YUXcvTRR5e7HAAAYBCoqKm5mzdvzrhx44TQAtXU1GTcuHG60AAAQGEqKogmEULLwNccAAAoUkVNzX2rKfOb+uVx2xobejzmtddeyzXXXJNnn302NTU1uf766/Pwww/nhz/8YYYPH56jjjoqS5YsyeGHH57vfve7+Yd/+IfS5z7zzDN58MEHU1dXl9WrV+eWW25JkowfPz433HBDjjjiiCTJnXfembvuuivDhg3L9OnTc9VVV3Wp45VXXskXvvCF/OpXv8rEiRNz0003ZezYsZ2OeeGFF/LTn/40Z5111j5f0xNPPJHhw4fn5JNP7vH1AwAA9KeK64hWgsWLF+f000/PmjVrsmrVqhx77LH54Ac/mNWrV+ehhx7KlClTSgHzYx/7WFatWpVVq1blr//6rzNx4sTU1dVl+/btWbx4cVasWJGHHnooJ5xwQu6+++4kyU9+8pM0NzfnoYceSlNTUy6++OJu61i+fHlOPfXUPPzwwzn11FOzfPnyLsf86le/yurVq3t8TU8++WR++tOfHsRXBQAAoG9UbEd0t950MHujtx3WN954I0899VQaGxuTJCNGjMiIESNy2mmnlY6ZNm1a1qxZ0+Vzm5qaMnv27CS7FgHq6OjIpk2b0tHRkTfeeCOTJ09Oktxzzz259NJLM2LEiCTJuHHjuq2lubk5d955Z5Lk7LPPzoUXXpgrr7yy0zE33nhjfvnLX2bOnDn5+Mc/no6Ojjz77LNZsmRJnnnmmVxxxRW56aabcu+992bIkCH57ne/m2uvvTbve9/7evX1AAAA6GsVH0SLtmHDhhxxxBFZsGBBnn766Zx44om5+uqrM3r06NIxDzzwQM4888wun/tP//RP+bu/+7skyfDhw7Nw4cKcddZZGT16dCZPnpyvfvWrSZK2trb867/+a/7mb/4mI0eOzFVXXZX3vve9XR7vpZdeyvjx45Psmtr7m9/8pssxV1xxRW6//fZSh3bnzp258MIL88gjj2TZsmX52te+luOOOy6f+MQnMnr06L12XwEAAIpiau4etm/fnvXr1+f888/PypUrM2rUqE5TYpctW5ahQ4fmYx/7WKfP+/nPf55Ro0bl3e9+d5Jd10S95557snLlyjz22GM54YQTSmFxx44dee2113LfffflqquuyuWXX56Ojo4+qX/IkCFpbGzMVVddlfe///35gz/4gz55XAAAgL4iiO6htrY2tbW1mTp1apLkox/9aNavX58kefDBB/Poo4/mG9/4RpeVZpuamtLQ8PtpxK2trUmSo446KjU1NTnzzDNL52hOmDAhs2bNSk1NTd773vdmyJAhefnll7NgwYLMmTMnf/7nf55k15TdF198MUny4osvlhY66klbW1tGjx5d+lwAAIBKIoju4Z3vfGdqa2vz/PPPJ0kef/zxHHvssVm7dm1uvfXWLFu2LKNGjer0OTt37syaNWs6BdEJEybkl7/8ZWk67Y9//OMce+yxSZKPfOQj+clPfpIk+fd///ds27Ytb3/727NkyZKsWrUqt956a5JkxowZWblyZZJk5cqVmTlzZpd6Dz300Lz55pul7ddffz2LFy/OXXfdlVdeeaV0LuuexwEAAJRLxZ8j2l+XcdmXa6+9Nl/60peybdu2TJo0KUuWLMm5556brVu35qKLLkqSTJ06NYsWLUqSPPXUU6mtrc2kSZNKjzFhwoT8xV/8RS644IIMGzYsEydOzJIlS5Ik55xzTr7yla9k9uzZGT58eBobG7u9luell16ayy+/PPfff3+OPPLILF26NEnS0tKSe++9N4sXL84JJ5xQmio8d+7cPPPMM/nkJz+Zo48+OosXL86nPvWpnHLKKfnwhz+cyy67LM3NzRYrAgAAyqqmo69OTtxPra2tqaur63T7rfvKeR3RweitX3v6l681g4nxzmBivDOYGO/0xr7GScV2RAVGAACAganHc0QXLFiQU089tXR9zD11dHTk61//embNmpWzzjor//Zv/9bnRQIAADBw9BhE586dm9tuu22v969duzZtbW15+OGH81d/9VdZuHBhX9YHAADAANNjED3llFMyduzYvd7f3Nycs88+OzU1NZk2bVpee+01lw0BAABgrw76HNH29vbU1taWtmtra9Pe3p7x48cf7EMDAAxo5bg6AJWhItdDWbj35tOeKnaZooWvlruCflO/or7LvpZ5LWWopG8cdBDtbtHd7i5FsqctW7aktbU1SbJ58+a0trZm27Zt2bRp08GWxAHYtm1b6f+D/rV7vMNgYLyXz5krni93CYOUrzu909fvjXXf/kCfPl61Gmg/c/70yT/d5/3V/HoPOojW1tZm48aNpe2NGzf2qhs6cuTIbi/fMmrUqF0H7MdfZPZLL/5K8tprr+Waa67Js88+m5qamlx//fV5+OGH88Mf/jDDhw/PUUcdlSVLluTwww/Ptm3bcs0112T9+vXZvn17zj777HzmM59Jsmuhp0cffTTjxo3L6tWrS4//9NNP56tf/Wp++9vfZuLEifnGN76Rww47rEsNDz30UC644IJ91tra2poXX3wx06dPP4Avxu8NHz7cEtwFsdw5g4nxfuB0ygaXiuyO0S92f293+97YX7//7q9e/L5cce/vv/valaum7rqV/aVlXkvp+Srq/6Ab+wrKBx1EZ8yYkbvuuisNDQ35+c9/njFjxlT9tNzFixfn9NNPz80335ytW7dm8+bN+eAHP5grrrgiw4YNyw033JBbbrklV155ZdasWZOtW7fmoYceyqZNm9LQ0JCGhoa8613vyty5c/Nf/+t/zZe//OVOj3/11Vfny1/+ct7//vfn/vvvz2233ZbLL7+80zGvvfZa7rnnnl4F0V/84hcHHUQBKL/+CJ8CTnEq7hdz9l8BQbDtkN3P1U9PMACmph5QqDv6qF0fCwyERarmKbh702MQ/eIX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hg0bcsQRR2TBggV5+umnc+KJJ+bqq6/O6NGjS8c88MADOfPMM5MkZ5xxRpqbm3Paaadl8+bNWbBgQd72trd1esympqbMnj2707677747K1euzEknnZT58+d3mXKbJC+99FLGjx+fJBk/fnx+85vfdDnmiiuuyO23355bbrklSbJz585ceOGFeeSRR7Js2bJ87Wtfy3HHHZdPfOITGT16dI9dWAD6xv4sTAQAg82QchdQabZv357169fn/PPPz8qVKzNq1KgsX768dP+yZcsydOjQfOxjH0uSrFu3LkOGDMljjz2W5ubm3H777dmwYUPp+K1bt+YHP/hBPvrRj5b2nX/++XnkkUeyatWqjB8/vtR97QtDhgxJY2Njrrrqqrz//e/PH/zBH/TZYwPQf1rmtZT+AcBAJ4juoba2NrW1tZk6dWqS5KMf/WjWr1+fJHnwwQfz6KOP5hvf+EZqamqSJKtXr87pp5+e4cOHZ9y4cTn55JPT0vL7XyLWrl2bE088Me94xztK+97xjndk6NChGTJkSM4777zS8QsWLMicOXPy53/+50mScePG5cUXX0ySvPjii6XFjXrS1taW0aNHlz4XgMpSv6K+9A8ABiNBdA/vfOc7U1tbm+effz7JrvM7jz322Kxduza33nprli1bllGjRpWOP/LII/PEE0+ko6Mjv/3tb/Pzn/88xxxzTOn+pqamNDR0np711oD4/e9/P8cff3ySZMmSJVm1alVuvfXWJMmMGTOycuXKJMnKlSszc+bMLvUeeuihefPNN0vbr7/+ehYvXpy77rorr7zySulc1j2PAwAAKJeKP0e0HH8tvvbaa/OlL30p27Zty6RJk7JkyZKce+652bp1ay666KIkydSpU7No0aJccMEFWbBgQWbPnp2Ojo7MnTs373nPe5IkmzZtyr/8y79k0aJFnR7/hhtuyNNPP50kmThxYpf7d7v00ktz+eWX5/7778+RRx6ZpUuXJklaWlpy7733ZvHixTnhhBNKU4Xnzp2bZ555Jp/85Cdz9NFHZ/HixfnUpz6VU045JR/+8Idz2WWXpbm52WJFABXCNFwABquKD6LlUFdXl+985zud9j3yyCPdHnvooYfm5ptv7va+UaNG5Yknnuiy/4YbbuhVHW9/+9uzYsWKLvvr6+tTX78roA8fPrzbY5Jd3drddY8bNy4PPfRQr54X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2tps3LixtN3e3p7x48d3OebXv/51amtrs3379rz++ut529vets/HnTZtWk9PDQAAwADU49Tc+vr6tLW1ZcOGDdm6dWuampoyY8aMTsfMmDEjDz74YJLkn//5n/OBD3ygx44oAAAAg1NNRy9WFfrRj36U66+/Pjt27Mg555yTz372s1m6dGlOOumkzJw5M1u2bMmVV16Z1tbWjB07Nn/zN3+TSZMmFVE/AAAAVaZXQRQAAAD6So9TcwEAAKAvCaIAAAAUquxBdO3atTnjjDMya9asLF++vNzlQJ+aMWNGzjrrrMyZMydz585Nkrzyyiu56KKL8sd//Me56KKL8uqrr5a5SjhwCxYsyKmnnprZs2eX9u1tjHd0dOTrX/96Zs2albPOOiv/9m//Vq6yYb91N9b/9m//NqeffnrmzJmTOXPm5Ec/+lHpvltuuSWzZs3KGWeckccee6wcJcMB+/Wvf50LL7wwZ555ZhoaGrJixYok3t/pW2UNojt27MiiRYty2223pampKatXr85zzz1XzpKgz61YsSKrVq3Kd77znSTJ8uXLc+qpp+bhhx/Oqaee6g8wVLW5c+fmtttu67Rvb2N87dq1aWtry8MPP5y/+qu/ysKFC8tQMRyY7sZ6knz605/OqlWrsmrVqkyfPj1J8txzz6WpqSlNTU257bbb8rWvfS07duwoumQ4YEOHDs38+fPzve99L9/+9rfzrW99K88995z3d/pUWYPounXrMnny5EyaNCkjRoxIQ0NDmpuby1kS9Lvm5uacffbZSZKzzz473//+98tcERy4U045JWPHju20b29jfPf+mpqaTJs2La+99lpefPHFwmuGA9HdWN+b5ubmNDQ0ZMSIEZk0aVImT56cdevW9XOF0HfGjx+fE088MUly2GGH5Zhjjkl7e7v3d/pUWYNoe3t7amtrS9sTJkxIe3t7GSuCvnfxxRdn7ty5+fa3v50keemllzJ+/Pgku97of/Ob35SzPOhzexvje77n19bWes+n6t19990566yzsmDBgtI0Rb/fMJC88MILaW1tzdSpU72/06fKGkS7u3JMTU1NGSqB/nHPPffkwQcfzK233pq77747Tz31VLlLgrLxns9Ac/755+eRRx7JqlWrMn78+DQ2NiYx1hk43nzzzVx22WX5yle+ksMOO2yvxxnzHIiyBtHa2tps3LixtN3e3l76KwsMBBMmTEiSjBs3LrNmzcq6desybty40nSVF198MUcccUQ5S4Q+t7cxvud7/saNG73nU9Xe8Y53ZOjQoRkyZEjOO++8tLS0JPH7DQPDtm3bctlll+Wss87KH//xHyfx/k7fKmsQra+vT1tbWzZs2JCtW7emqakpM2bMKGdJ0Gd++9vf5o033ijd/vGPf5zjjz8+M2bMyMqVK5MkK1euzMyZM8tZJvS5vY3x3fs7Ojrys5/9LGPGjPGLClXtrefAff/738/xxx+fZNdYb2pqytatW7Nhw4a0tbXlve99b7nKhP3W0dGRq6++Osccc0wuuuii0n7v7/Slmo7ueukF+tGPfpTrr78+O3bsyDnnnJPPfvaz5SwH+syGDRvyF3/xF0l2rRA9e/bsfPazn83LL7+cyy+/PL/+9a9z5JFHZunSpXnb295W5mrhwHzxi1/Mk08+mZdffjnjxo3L5z73uXzkIx/pdox3dHRk0aJFeeyxxzJq1Khcf/31qa+vL/dLgF7pbqw/+eSTefrpp5MkEydOzKJFi0q/fC9btiwPPPBAhg4dmq985SulFXWhGvzrv/5rLrjggrz73e/OkCG7+lZf/OIX8973vtf7O32m7EEUAACAwaWsU3MBAAAYfARRAAAACiWIAgAAUChBFAAAgEIJogAAABRKEAUAAKBQgigAAACFEkQBAAAo1P8H1e4w8gtOSr8AAAAASUVORK5CYII=\n", 96 | "text/plain": [ 97 | "
" 98 | ] 99 | }, 100 | "metadata": {}, 101 | "output_type": "display_data" 102 | } 103 | ], 104 | "source": [ 105 | "for file, sample in file_means.items():\n", 106 | " sns.distplot(round(sample),\n", 107 | " norm_hist=True, \n", 108 | " kde=False,\n", 109 | " hist_kws={'histtype':'step', 'cumulative': True, 'linewidth':2, 'alpha':1},\n", 110 | " kde_kws={'cumulative': True}, \n", 111 | " bins=256)\n", 112 | " \n", 113 | "plt.legend(file_means.keys(), loc=\"upper left\")\n", 114 | "plt.show()" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 20, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "def plot_dist_hist(data):\n", 124 | " sns.distplot(data,\n", 125 | " kde=False,\n", 126 | " hist_kws={'alpha':1},\n", 127 | " bins=256)\n", 128 | " plt.show()" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 35, 134 | "metadata": {}, 135 | "outputs": [ 136 | { 137 | "data": { 138 | "image/png": "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Pp4PfOAD+c53vpPVq1cnSe67774MDw83ZaAAAAC0noZP312yZEl+7/d+L7/+67+e7u7ufOhDH8qtt96ahQsXpqvrndX29vZmdHT0guuamJjIyMhIkmR8fHz6Nq2nr6/vjPvt/lqb77ST8fHx9y0z/2lV9u+0E/Od2Wo4Sk+cOJHh4eEMDw/n2muvzR/90R/lwIED73tcR0fHBdfV3d09HSsjIyPvCxdaV7u/1uY77eRsP7CY/7Qq+3faifnOTJzvFxcNR+m//Mu/5Bd+4ReyaNGiJMmqVavyve99LydPnszbb7+drq6uHD16ND09PY1uAgAAgBbX8DWlP/dzP5f//M//zP/+7/9mamoq3/72t/PBD34wd955Z77xjW8kSV544YUMDAw0bbAAAAC0loaPlC5fvjyrV6/Offfdl66urvT19eW3f/u382u/9mv54z/+4/zVX/1V+vr6snHjxmaOFwAAgBYyq88p3bRpUzZt2nTGsqVLl/oYGAAAAGZkVh8JAwAAALMhSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgEAACgjSgGgicZPT571NgBwdl3VAwCAVnLVlZ1ZtnUoSXL4ybXFowGA+c+RUgAAAMqIUgAAAMqIUgAAAMqIUgAAAMqIUgAAAMqIUgAAAMrMKkpPnjyZTZs25e67786aNWvyve99L8ePH8/g4GBWrVqVwcHBnDhxolljBQAAoMXMKkoff/zxfPjDH87f//3fZ+/evbnpppuyY8eO9Pf3Z9++fenv78+OHTuaNVYAAABaTMNR+tZbb+W73/1u7r///iTJggUL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\n", 139 | "text/plain": [ 140 | "
" 141 | ] 142 | }, 143 | "metadata": {}, 144 | "output_type": "display_data" 145 | } 146 | ], 147 | "source": [ 148 | "utf8_reference = round(file_means['62875-0.txt'])\n", 149 | "plot_dist_hist(utf8_reference)" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 45, 155 | "metadata": {}, 156 | "outputs": [], 157 | "source": [ 158 | "# replace \" \" with 0\n", 159 | "\n", 160 | "for index, value in enumerate(utf8_reference):\n", 161 | " if value == 32:\n", 162 | " utf8_reference[index] = 0" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": 46, 168 | "metadata": {}, 169 | "outputs": [ 170 | { 171 | "data": { 172 | "image/png": "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TGRsbCzvvPNOxsbG8oEPfCDf+c53snbt2iTJAw88kMHBwVkZFAAAgObT8Om7y5Yty+/93u/l13/919Pe3p4PfehDuf3227N48eK0tb272c7OzoyMjFx0W+Pj4xkaGmp0lMuiq6vrnPvzfV44n7GxMeuXpnFmPTtGs9A5NtNMrGcuVcNReuLEiQwODmZwcDDXX399/uiP/igHDx583/u1tLRcdFvt7e3v+4Vivlto88IZU/0CDwvV+dazNc5C49hMM7GemcqF/lDRcJT+y7/8S37hF34hS5YsSZKsWbMm3/ve93Ly5Mm88847aWtry9GjR9PR0dHoLgAAAGhyDb+m9Od+7ufyn//5n/nf//3fTE5O5tvf/nY++MEP5u677843vvGNJMmLL76Ynp6eWRsWAACA5tLwM6UrV67M2rVr88ADD6StrS1dXV357d/+7fzar/1a/viP/zh/9Vd/la6urmzatGk25wUAAKCJzOg6pZs3b87mzZvPeWz58uUuAwMAAMC0zOiSMAAAADATohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQAAIAyohQA5qmx0xNT3gaAZtJWPQAAMLVrrm7Nim0DSZLhnb3F0wDA3PBMKQAAAGVEKQAAAGVEKQAAAGVEKQAAAGVEKQAAAGVEKQAAAGVmFKUnT57M5s2bc++992bdunX53ve+l+PHj6evry9r1qxJX19fTpw4MVuzAgAA0GRmFKVPPvlkPvzhD+fv//7vs2/fvtxyyy3p7+9Pd3d39u/fn+7u7vT398/WrAAAADSZhqP07bffzne/+908+OCD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\n", 173 | "text/plain": [ 174 | "
" 175 | ] 176 | }, 177 | "metadata": {}, 178 | "output_type": "display_data" 179 | } 180 | ], 181 | "source": [ 182 | "plot_dist_hist(utf8_reference)" 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": 44, 188 | "metadata": {}, 189 | "outputs": [ 190 | { 191 | "data": { 192 | "text/plain": [ 193 | "" 194 | ] 195 | }, 196 | "execution_count": 44, 197 | "metadata": {}, 198 | "output_type": "execute_result" 199 | }, 200 | { 201 | "data": { 202 | "image/png": "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NDTE8uXL46233orXX389+cxwJOOd5yPp7e2NfD4fc+fOjdbW1li0aFH09/fP8IQwMc3NzfGZz3wmIiJOOOGEOPXUU6NcLvv+zJQI0Y8ol8uRy+VGH7e0tES5XK7iRHBsrrrqqlizZk088MADERHxxhtvRHNzc0R88AfKm2++Wc3xYMKOdHY/+v06l8v5fk1duPfee2P16tWxefPm0VsZ/f2DevHqq69GsViM008/3fdnpkSIfsR472bT0NBQhUng2N1///3xyCOPxE9/+tO499574w9/+EO1R4Jp5/s19ejSSy+NJ554Inbv3h3Nzc3R09MTEc4z9eHdd9+N6667Lm666aY44YQTjnid88xECNGPyOVyMTg4OPq4XC6P/p8eqBctLS0REbFgwYLo6uqK/v7+WLBgwehtMa+//nqcfPLJ1RwRJuxIZ/ej368HBwd9v6bmfeITn4g5c+bEcccdF+vWrYsXX3wxIvz9g9r33//+N6677rpYvXp1nHvuuRHh+zNTI0Q/or29PUqlUgwMDMTBgwejUChEZ2dntceCCXvvvffinXfeGf34mWeeiaVLl0ZnZ2c8+uijERHx6KOPxsqVK6s5JkzYkc7u/54fGRmJF154IebNm+cvOtS8D/+c3JNPPhlLly6NiA/Oc6FQiIMHD8bAwECUSqU47bTTqjUmjDEyMhLf/OY349RTT40rr7xy9Hnfn5mKhpHxduez3N69e2Pr1q0xPDwca9eujQ0bNlR7JJiwgYGB+MpXvhIRH7wK9AUXXBAbNmyI/fv3x/XXXx+vvfZaLFy4MLZt2xYnnXRSlaeFsW644YZ4/vnnY//+/bFgwYK49tpr45xzzhn37I6MjER3d3c8/fTTcfzxx8fWrVujvb292v8KMGq88/z888/HX//614iIOOWUU6K7u3v0L+h33nlnPPzwwzFnzpy46aabRl9RF6rtj3/8Y1x22WXxqU99Ko477oM91g033BCnnXaa788cMyEKAABAUm7NBQAAICkhCgAAQFJCFAAAgKSEKAAAAEkJUQAAAJISogAAACQlRAEAAEhKiAIAAJDU/wFFcWsNpDdkGQAAAABJRU5ErkJggg==\n", 203 | "text/plain": [ 204 | "
" 205 | ] 206 | }, 207 | "metadata": {}, 208 | "output_type": "display_data" 209 | } 210 | ], 211 | "source": [ 212 | "sns.distplot(utf8_reference,\n", 213 | " norm_hist=True, \n", 214 | " kde=False,\n", 215 | " hist_kws={'histtype':'step', 'cumulative': True, 'linewidth':2, 'alpha':1},\n", 216 | " kde_kws={'cumulative': True}, \n", 217 | " bins=256)" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": 43, 223 | "metadata": {}, 224 | "outputs": [], 225 | "source": [ 226 | "with open(\"utf8_english\", \"wb\") as f:\n", 227 | " pickle.dump(utf8_reference, f)" 228 | ] 229 | } 230 | ], 231 | "metadata": { 232 | "kernelspec": { 233 | "display_name": "Python 3", 234 | "language": "python", 235 | "name": "python3" 236 | }, 237 | "language_info": { 238 | "codemirror_mode": { 239 | "name": "ipython", 240 | "version": 3 241 | }, 242 | "file_extension": ".py", 243 | "mimetype": "text/x-python", 244 | "name": "python", 245 | "nbconvert_exporter": "python", 246 | "pygments_lexer": "ipython3", 247 | "version": "3.7.6" 248 | } 249 | }, 250 | "nbformat": 4, 251 | "nbformat_minor": 2 252 | } 253 | -------------------------------------------------------------------------------- /notebooks/archive/readme.txt: -------------------------------------------------------------------------------- 1 | The notebooks in this directory have not been updated to reflect changes made to the code. 2 | -------------------------------------------------------------------------------- /scripts/basic_DBSCAN_clustering.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path[0:0] = ['.', '..'] 3 | 4 | from centrifuge.binfile import BinFile 5 | 6 | def cluster(): 7 | with open("/bin/ls", "rb") as f: 8 | binfile = BinFile(f) 9 | binfile.slice_file() 10 | 11 | binfile.cluster_DBSCAN(0.9, 10, find_optimal_epsilon=True) 12 | binfile.plot_DBSCAN_results() 13 | 14 | if __name__=="__main__": 15 | cluster() 16 | -------------------------------------------------------------------------------- /scripts/entropy_plot.py: -------------------------------------------------------------------------------- 1 | 2 | #!~/anaconda3/bin/python3 3 | 4 | 5 | import sys 6 | sys.path[0:0] = ['.', '..'] 7 | 8 | from centrifuge.binfile import BinFile 9 | 10 | def visualize_file_entropy(): 11 | with open("/bin/bash", "rb") as f: 12 | binfile = BinFile(f) 13 | binfile.slice_file() 14 | binfile.plot_file_entropy() 15 | 16 | if __name__ == "__main__": 17 | visualize_file_entropy() 18 | -------------------------------------------------------------------------------- /scripts/entropy_plot_text_section.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path[0:0] = ['.', '..'] 3 | 4 | from centrifuge.binfile import BinFile 5 | 6 | def label_text_section(): 7 | with open("/bin/bash", "rb") as f: 8 | binfile = BinFile(f) 9 | binfile.slice_file() 10 | binfile.plot_file_entropy(0x2cbc0, 0x2cbc0 + 0xa2c02) 11 | 12 | if __name__ == "__main__": 13 | label_text_section() 14 | 15 | -------------------------------------------------------------------------------- /scripts/identify_clusters.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path[0:0] = ['.', '..'] 3 | 4 | from centrifuge.binfile import BinFile 5 | 6 | def cluster(): 7 | with open("/bin/ls", "rb") as f: 8 | binfile = BinFile(f) 9 | binfile.slice_file() 10 | binfile.cluster_DBSCAN(0.9, 10, find_optimal_epsilon=False) 11 | results = binfile.identify_cluster_data_types() 12 | print(results) 13 | 14 | if __name__=="__main__": 15 | cluster() 16 | 17 | -------------------------------------------------------------------------------- /scripts/plot_all_variables.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path[0:0] = ['.', '..'] 3 | 4 | from centrifuge.binfile import BinFile 5 | 6 | 7 | def plot_all(): 8 | with open("/bin/bash", "rb") as f: 9 | binfile = BinFile(f) 10 | binfile.slice_file() 11 | 12 | binfile.show_scatter_matrix() 13 | print(binfile.file_data_frame) 14 | 15 | 16 | if __name__ == "__main__": 17 | plot_all() 18 | 19 | -------------------------------------------------------------------------------- /scripts/plot_cluster_cdfs.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import seaborn as sns 3 | sns.set_style("whitegrid") 4 | 5 | import sys 6 | sys.path[0:0] = ['.', '..'] 7 | 8 | from centrifuge.binfile import BinFile 9 | 10 | def cluster(): 11 | with open("/bin/ls", "rb") as f: 12 | binfile = BinFile(f) 13 | binfile.slice_file() 14 | binfile.cluster_DBSCAN(0.9, 5, find_optimal_epsilon=False) 15 | #binfile.plot_DBSCAN_results() 16 | 17 | binfile.plot_cluster_cdfs() 18 | 19 | 20 | if __name__=="__main__": 21 | cluster() 22 | -------------------------------------------------------------------------------- /scripts/plot_two_variables.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path[0:0] = ['.', '..'] 3 | 4 | from centrifuge.binfile import BinFile 5 | 6 | def plot(): 7 | with open("/bin/bash", "rb") as f: 8 | binfile = BinFile(f) 9 | binfile.slice_file() 10 | 11 | binfile.plot_variables_by_range(binfile.block_entropy_levels, 12 | binfile.block_byteval_std_dev, 13 | 0x2cbc0, 0x2cbc0 + 0xa2c02, 14 | title="bash", 15 | xlabel="entropy", 16 | ylabel="byte value standard deviation") 17 | 18 | if __name__ == "__main__": 19 | plot() 20 | -------------------------------------------------------------------------------- /scripts/readme.txt: -------------------------------------------------------------------------------- 1 | example: 2 | 3 | $ python3 plot_all_variables.py 4 | -------------------------------------------------------------------------------- /scripts/small_elf.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import seaborn as sns 3 | sns.set_style("whitegrid") 4 | 5 | import sys 6 | sys.path[0:0] = ['.', '..'] 7 | 8 | from centrifuge.binfile import BinFile 9 | 10 | def cluster(): 11 | with open("/bin/cat", "rb") as f: 12 | cat = BinFile(f) 13 | cat.slice_file() 14 | cat.cluster_DBSCAN(1, 3, find_optimal_epsilon=True) 15 | cat.plot_DBSCAN_results() 16 | 17 | cat.identify_cluster_data_types() 18 | 19 | if __name__=="__main__": 20 | cluster() 21 | --------------------------------------------------------------------------------