├── 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/vRYtWhQVRCXpmmuu0Z49e9SuXbuo4yNGjNBrr72mM888U1u2bFFTU5PKy8v10EMPRc5ZsGCBOnbsaBlCYw0ePFgzZ87U1q1b1adPHzU0NGjHjh3q27dv1Hnnnnuu5s+fr4suukjHHHOMdu7cqaKi+H/tTq9ndM4552jZsmWRNuXPP/88qesAANLI4/DJOlBz+TZsyGh8zP7yYWVlZZHHxipoXPg0tNbG+tvIK+OPWQxkHl692XQaLtKv6q91mb4FZKHAB1E/rVq1SlOmTIk6NnLkSN17771Rx0aMGKEFCxZofcxvnN/4xjdMrztmzBj94he/0IUXXqji4mJP9lnt0qWLZs+ereuuu06HDh2SJF177bWmQXTz5s2RimjHjh31m9/8Jm5CrtPrGV199dWaOXOmLrzwQhUWFqq2tlYjR450fR0AQAo83HIF7gQlfPr1P+1f+ZPhf6B/9KnpOcbwWVlZaX0xi/CZapttGFVQIPgKWlpaWjLxwWb9+EHr0W9oaFCHDh0yfRswCNp3JF3y5edE8PDdywIZHjwUroh6XQXNlu9ePoRPY+A0sgufpsHTh0FCVnuCOpUt370gCq8RpSKaHDffvdj/5ictvFWSVFrSXYtqaizPy7REPyMVUQAAcgFV0LRwst7Tj/Dp9V8urcJmLNfhM6bSmWz4ZIItkPsIogGzd+9eXXnllXHHH3jgAZWXl/t/QwCAzMtw+63VdNx8li1rPp0GTiO34XP17XvbnjgInrEhc9vRgUS95gxxfI+pVkEBZB5BNGDKy8v1xBNPZPo2AADIW5nccsUo2Sqom/B59tgJkqwHDoWVlZXp2LLdkqR9+3frjXt6ubonq/AJIH8RRAEACKIADiHK5em4mdxyxchp+HRb6QwHzrBI8EwQQI3hU7IPn5lop6UKCmQvgigAAMhL2TZ4KNXwmUhJaYMkqWtX4xZ05uEz2cBpVgVNtR0XQPYiiAIAEGQMIfJUUMJnIsm01jphbL8NB08pHD7bAqiX4RMArAQ2iKYy4jsRfiMFAASWk61Z4FoQw2coFEpqkJBkHz6tAqdK2x4aK5/G4Dm8erOGJ3VXbezWfzKUCIAU4CCaSffcc49WrlypgoICtWvXTjNnztTcuXM1Y8YMDRzY+hvitm3b9JOf/EQrV66MvG/79u0aPXq0amtr9eMf/zjl+1i/fr1+9atfqaioSMuXL1f79u1TviYAAMhM+Bx5+lddne+m2hneU1BSVOC006lsoIZXr7c/0Se03wL5I/BB1KsKptMK61tvvaUXX3xRjz/+uJqbm9XQ0KCmpiZH7509e7aGDHH+f/laWlrU0tKiwsJC09effPJJXXXVVRozZown1wMAZAnacVNiVQHNRPicN+5C23OcBs6osOlSaUn36G1X1nsXPq0qoG4qn3aoggZH3alVmb4F5IjAB1G/7dq1S+Xl5SopKVFDQ4O6dOni6H3PP/+8evXqpY4dOyY8b9u2bZoyZYrOPPNMvf3227rrrru0ZcsWLViwQIcOHVLv3r01e/ZsPfXUU3rmmWe0du1avfLKK5o3b57uv/9+Pf300zp06JDOP/98XXPNNY6vd8wxx6i6ulqXXHKJXnjhBR0+fFh33HGHTjrpJH355Ze69dZbtWnTJklSbW2tRo0apbVr15peBwCAoAnK1FupLXw+u+k909eTrnQ6UFrSPep55fbtrt7vlJfbr9B+C+QngmiMc845R3fddZdGjRqlb33rW7r44ov17W9/W5J0/fXXR9pjm5qaIpXHAwcO6Pe//70WL16sxYsX237Gli1bNHv2bIVCIe3Zs0f33HOPlixZoo4dO2rRokVasmSJamtr9eabb2rYsGG64IILtHbtWm3dulWPPvqoWlpadPXVV+uNN95Qz549HV9Pat2n9PHHH9cf/vAHLV68WLNmzdLdd9+tY489VitWrJAkff7557bXAQB4hHWhaeNnBTTV8OkmcB5q7BB5XFZWFvVaZWWlL9VOM0zAzS9Vf63L9C0gyxFEYxxzzDH685//rPXr12vt2rX6+c9/runTp0uS5s6dG7dGVJIWLFigSZMmOa4WVlZWavDgwZKkd955Rx988IHGjx8vqTXghl8zWrdundatW6dLLrlEUmv4ra+vV8+ePV1db+TIkZKkAQMG6LnnnpMkvfrqq/rtb38bOadz58564YUXHN0XAACZYlYFzYfwKUn/csYZkqRhw4ZFHXe6FUwimQqfVEGB/EIQNdGuXTudeeaZGjRokE4//XT953/+Z8Lz33nnHT377LOaO3eu9u3bp8LCQpWWlupHP/qR6fnG9t2Wlhadc845UUHQTEtLi2pqavSDH/wg6vi2bdtcXa+4uFiSVFhYqObm5sh7CgoK4j7PyX0BADzCulBHMt2Cm0r4dBI8Y1tr9+/fH3lsDJ/DDOckEz7dttYmu96T8AnASuCDaLq2cbHy4YcfqrCwUCeeeKIkqa6uTpWVlXr//fct3/PQQ23j1xcsWKCOHTtahtBYgwcP1syZM7V161b16dNHDQ0N2rFjh/r27Rt13rnnnqv58+froosu0jHHHKOdO3eqqCj+X5/T6xmdc845WrZsmW666SZJra25yVwHAIBM8LoKajVgKFH4jITMJAYKxYbPCV+Nn66bbPhMZS0n4RNAOgU+iPrtwIEDuvXWWyOVzRNPPFEzZ87UtGnT0vJ5Xbp00ezZs3Xdddfp0KFDkqRrr73WNIhu3rw5UhHt2LGjfvOb38RNyHV6PaOrr75aM2fO1IUXXqjCwkLV1tZq5MiRrq8DAMgdd/1kTaZvIcKPFlwn022NUgmfscFTMg+fsRKFz2QDpxeTbZ2s9yR8AohV0NLS0pKJD66rq1NVVZXtsUxqaGhQhw4d7E+Eb4L2HUmXfPk5ETx89zIgPKwoYK25ZkH0p/dWp+3zEn33MhVEpy9fGRf+7NprzUKmmUU1NVHPX3zxxbhzYtd/Tj443NG1zfgVOI2yJXzy+5474e1bGFaUOjffPavfi0pLukf9fuLFOnEvJfoZqYgCAOC3LJqUm87wmUimwuf+qm9GHodCoaTWdcaGTDeGDRumfc9vlSTte36rrjv3Klfv93LvTsld+MyW4AkgGAiiabJ3715deeWVcccfeOABlZeX+39DAAAE2PeWfijpw7R/jln4fHbTe7pv59HPXv2Mo+t4ET7NqqBOwqfXYTMWazwRK1wFBbwUmCAaCoW0a9cudevWzffPTYfy8nI98cQTabk2ACBHBKwd129OJuB6UQW1C5+XObiGWcutk/AZrm6asvlbWLoDpxHhE4DfAhNEAQBAZgVhQFG6gqfkLHwmWuOZKHgmDJxH/W/RFtPjZpXRdMjVNZ7wD+tC4SWCKAAASCu7ymf9nNEpD4wxhs8pbz8Xebx7d3PksdPwaQycUQHTQdi00mlEH+nFtiAaO4jIS24DpxHhE2G04yLdCKIAACBKpgYUueUkfFqJDZ9z+41qe5Jq4LQQDp9eLAsibMJrBE/4jSAKAEAeS1c7brqm3pqFz927m7Xb5FyzNtvmfa17YzcfPKTfDb7I8ecmCpjpROBEptGOi3QhiAIAAE84GT6UjHD4jJpuK1mGz2TDpuRN4DRWPJ1WP5MNnIRNpMKsCkrwhF8IojG2bdumyZMn64wzztBbb72lqqoqjRkzRnfeeaf27NmjuXPnqn///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 | "