HAL  v4.5.0-83-g30c8f0afc
The Hardware Analyzer - a comprehensive reverse engineering and manipulation framework for gate-level netlists.
sbox_lookup.cpp
Go to the documentation of this file.
1 #include "hawkeye/sbox_lookup.h"
2 
10 
11 #include <algorithm>
12 #include <vector>
13 
14 namespace hal
15 {
16  namespace hawkeye
17  {
19  {
20  log_info("hawkeye", "start locating S-boxes within round function candidate...");
21  auto start = std::chrono::system_clock::now();
22 
23  const auto* nl = candidate->get_netlist();
24  const auto* graph = candidate->get_graph();
25 
26  // get initial set of components
27  auto comp_res = graph_algorithm::get_connected_components(graph, false).map<std::vector<std::vector<Gate*>>>([graph](const auto& comps) -> Result<std::vector<std::vector<Gate*>>> {
28  std::vector<std::vector<Gate*>> res;
29  for (const auto& c : comps)
30  {
31  if (const auto gates_res = graph->get_gates_from_vertices(c); gates_res.is_ok())
32  {
33  res.push_back(gates_res.get());
34  }
35  else
36  {
37  return ERR(gates_res.get_error());
38  }
39  }
40  return OK(res);
41  });
42  if (comp_res.is_error())
43  {
44  return ERR(comp_res.get_error());
45  }
46  auto components = comp_res.get();
47 
48  const auto& state_input_reg = candidate->get_input_reg();
49  const auto& state_output_reg = candidate->get_output_reg();
50 
51  std::vector<SBoxCandidate> res;
52 
53  for (const auto& component : components)
54  {
55  // gather FFs of the component that are also part of the state input reg
56  std::set<Gate*> component_input_ffs;
57  for (auto* comp_g : component)
58  {
59  if (state_input_reg.find(comp_g) != state_input_reg.end())
60  {
61  component_input_ffs.insert(comp_g);
62  }
63  }
64 
65  u32 number_input_ffs = component_input_ffs.size();
66  if (number_input_ffs < 3)
67  {
68  // too small for S-box
69  continue;
70  }
71  else if (number_input_ffs <= 8)
72  {
73  // assume to have found a single S-box
74  std::set<Gate*> sbox_output_gates;
75 
76  for (auto* cand_gate : component)
77  {
78  // skip FFs
79  if (cand_gate->get_type()->has_property(GateTypeProperty::ff))
80  {
81  continue;
82  }
83 
84  // output gates are all combinational gates that have no other successors but the state output reg
85  const auto suc_gates = cand_gate->get_unique_successors();
86  if (std::none_of(suc_gates.begin(), suc_gates.end(), [&state_output_reg](Gate* g) { return state_output_reg.find(g) == state_output_reg.end(); }))
87  {
88  sbox_output_gates.insert(cand_gate);
89  }
90  }
91 
92  // create S-box candidate if input size equals output size
93  if (sbox_output_gates.size() == number_input_ffs)
94  {
95  SBoxCandidate sbox_candidate;
96  sbox_candidate.m_candidate = candidate;
97  sbox_candidate.m_component = component;
98  sbox_candidate.m_input_gates = std::move(component_input_ffs);
99  sbox_candidate.m_output_gates = std::move(sbox_output_gates);
100  res.push_back(sbox_candidate);
101  }
102  continue;
103  }
104 
105  // try to split component in smaller sub-components (assuming component is combination of S-box and linear layer)
106  std::set<Gate*> current_subset = component_input_ffs;
107  std::vector<std::vector<std::set<Gate*>>> input_groupings;
108 
109  // abuse that map keys are sorted, hence rbegin() will return max distance in map
110  const auto& longest_dist_to_gates = candidate->get_longest_distance_to_gate();
111  for (u32 step = 1; step < longest_dist_to_gates.rbegin()->first + 1; step++)
112  {
113  if (const auto dist_it = longest_dist_to_gates.find(step); dist_it != longest_dist_to_gates.end())
114  {
115  const auto& new_gates = std::get<1>(*dist_it);
116  current_subset.insert(new_gates.begin(), new_gates.end());
117  }
118  else
119  {
120  break;
121  }
122 
123  // generate subgraph of new sub-component
124  auto subgraph_res = graph_algorithm::get_subgraph(graph, current_subset);
125  if (subgraph_res.is_error())
126  {
127  return ERR(subgraph_res.get_error());
128  }
129  auto subgraph = std::move(subgraph_res.get());
130 
131  auto comp_res = graph_algorithm::get_connected_components(subgraph.get(), false);
132  if (comp_res.is_error())
133  {
134  return ERR(comp_res.get_error());
135  }
136 
137  // determine input groups feeding into distinct sub-circuits
138  std::set<u32> lens;
139  std::vector<std::vector<Gate*>> subcomponents;
140  std::vector<std::set<Gate*>> input_groups;
141  for (const auto& comp : comp_res.get())
142  {
143  auto gates_res = subgraph->get_gates_from_vertices(comp);
144  if (gates_res.is_error())
145  {
146  return ERR(gates_res.get_error());
147  }
148  auto comp_gates = gates_res.get();
149 
150  // only consider sub-components connected to at least one input FF
151  if (std::any_of(comp_gates.begin(), comp_gates.end(), [&component_input_ffs](Gate* g) { return component_input_ffs.find(g) != component_input_ffs.end(); }))
152  {
153  std::set<Gate*> input_group;
154  for (auto* comp_g : comp_gates)
155  {
156  if (state_input_reg.find(comp_g) != state_input_reg.end())
157  {
158  input_group.insert(comp_g);
159  }
160  }
161  lens.insert(input_group.size());
162  input_groups.push_back(std::move(input_group));
163  subcomponents.push_back(std::move(comp_gates));
164  }
165  }
166 
167  // all input groups should have same size and comprise more than one input
168  if (lens.size() == 1 && input_groups.at(0).size() > 1 && input_groups.size() > 1)
169  {
170  input_groupings.push_back(input_groups);
171  }
172  }
173 
174  const auto& input_ffs_of_gate = candidate->get_input_ffs_of_gate();
175 
176  std::vector<std::pair<std::set<Gate*>, std::set<Gate*>>> sboxes_input_output_gates;
177  for (const auto& input_groups : input_groupings)
178  {
179  for (const auto& input_group : input_groups)
180  {
181  std::set<Gate*> output_group;
182  for (auto* comp_gate : component)
183  {
184  // disregard output FFs
185  if (state_output_reg.find(comp_gate) != state_output_reg.end())
186  {
187  continue;
188  }
189 
190  // disregard gates that only depend on a single input FF
191  if (input_ffs_of_gate.at(comp_gate).size() <= 1)
192  {
193  continue;
194  }
195 
196  // disregard gates that do not only depend on the input FFs of the sub-component
197  if (!std::includes(input_group.begin(), input_group.end(), input_ffs_of_gate.at(comp_gate).begin(), input_ffs_of_gate.at(comp_gate).end()))
198  {
199  continue;
200  }
201 
202  // disregard gates for which no successor is dependent on an additional (external) input
203  auto sucs = comp_gate->get_unique_successors();
204  if (std::all_of(sucs.begin(), sucs.end(), [&input_ffs_of_gate, &input_group](auto* sg) {
205  return std::includes(input_group.begin(), input_group.end(), input_ffs_of_gate.at(sg).begin(), input_ffs_of_gate.at(sg).end());
206  }))
207  {
208  continue;
209  }
210 
211  // disregard inverters at the outputs (should also be covered by next step)
212  auto preds = comp_gate->get_unique_predecessors();
213  if (comp_gate->get_type()->has_property(GateTypeProperty::c_inverter))
214  {
215  if (std::includes(output_group.begin(), output_group.end(), preds.begin(), preds.end()))
216  {
217  continue;
218  }
219  }
220 
221  output_group.insert(comp_gate);
222  }
223 
224  // disregard output gates that only depend on other output gates
225  std::vector<Gate*> to_delete;
226  for (auto* out_gate : output_group)
227  {
228  const auto pred_gates = out_gate->get_unique_predecessors();
229  if (std::all_of(pred_gates.begin(), pred_gates.end(), [output_group](Gate* g) { return output_group.find(g) != output_group.end(); }))
230  {
231  to_delete.push_back(out_gate);
232  }
233  }
234  for (auto* del_gate : to_delete)
235  {
236  output_group.erase(del_gate);
237  }
238 
239  if (input_group.size() <= 8 && output_group.size() <= 20)
240  {
241  if (output_group.size() == input_group.size() + 1)
242  {
243  for (auto* drop_gate : output_group)
244  {
245  SBoxCandidate sbox_candidate;
246  sbox_candidate.m_candidate = candidate;
247  sbox_candidate.m_component = component;
248  sbox_candidate.m_input_gates = input_group;
249  sbox_candidate.m_output_gates = output_group;
250  sbox_candidate.m_output_gates.erase(drop_gate);
251  res.push_back(sbox_candidate);
252  }
253  }
254  else if (output_group.size() == input_group.size() + 2)
255  {
256  for (auto drop_it_1 = output_group.begin(); drop_it_1 != output_group.end(); drop_it_1++)
257  {
258  for (auto drop_it_2 = std::next(drop_it_1); drop_it_2 != output_group.end(); drop_it_2++)
259  {
260  SBoxCandidate sbox_candidate;
261  sbox_candidate.m_candidate = candidate;
262  sbox_candidate.m_component = component;
263  sbox_candidate.m_input_gates = input_group;
264  sbox_candidate.m_output_gates = output_group;
265  sbox_candidate.m_output_gates.erase(*drop_it_1);
266  sbox_candidate.m_output_gates.erase(*drop_it_2);
267  res.push_back(sbox_candidate);
268  }
269  }
270  }
271  else
272  {
273  SBoxCandidate sbox_candidate;
274  sbox_candidate.m_candidate = candidate;
275  sbox_candidate.m_component = component;
276  sbox_candidate.m_input_gates = std::move(input_group);
277  sbox_candidate.m_output_gates = std::move(output_group);
278  res.push_back(sbox_candidate);
279  }
280  }
281  }
282  }
283  }
284 
285  auto duration_in_seconds = std::chrono::duration<double>(std::chrono::system_clock::now() - start).count();
286  log_info("hawkeye", "located {} S-box candidates within round function candidate in {:.2f} seconds", res.size(), duration_in_seconds);
287 
288  return OK(res);
289  }
290 
292  {
293  log_info("hawkeye", "start identifying S-box candidate...");
294  auto start = std::chrono::system_clock::now();
295 
296  std::string sbox_name;
297 
298  const RoundCandidate* candidate = sbox_candidate.m_candidate;
299  const std::vector<Gate*>& component = sbox_candidate.m_component;
300  const std::set<Gate*>& input_gates = sbox_candidate.m_input_gates;
301  const std::set<Gate*>& output_gates = sbox_candidate.m_output_gates;
302 
303  if (input_gates.size() == 0)
304  {
305  return ERR("empty set of input gates provided");
306  }
307 
308  const auto* nl = candidate->get_netlist();
309  const auto snd = SubgraphNetlistDecorator(*nl);
310 
311  std::vector<BooleanFunction> bfs;
312  std::set<Net*> all_inputs;
313 
314  const auto& in_reg = candidate->get_input_reg();
315  std::map<std::pair<u32, const GatePin*>, BooleanFunction> cache;
316 
317  for (const auto* out_gate : output_gates)
318  {
319  // check whether any of the output gates have multiple outputs (this could be relaxed later)
320  const auto& fan_out_nets = out_gate->get_fan_out_nets();
321  if (fan_out_nets.size() != 1)
322  {
323  log_error("hawkeye", "gate '{}' with ID {} has none or multiple fan-out nets, which is currently not supported", out_gate->get_name(), out_gate->get_id());
324  }
325  const auto* out_net = fan_out_nets.front();
326 
327  // get Boolean functions of all subgraphs described by the gates of the component and each output net
328  std::vector<Gate*> subgraph_gates;
329  std::copy_if(component.begin(), component.end(), std::back_inserter(subgraph_gates), [&in_reg](Gate* g) { return in_reg.find(g) == in_reg.end(); });
330  auto bf_res = snd.get_subgraph_function(subgraph_gates, out_net, cache);
331  if (bf_res.is_error())
332  {
333  return ERR(bf_res.get_error());
334  }
335  bfs.push_back(bf_res.get());
336 
337  // gather all input nets actually used by the component
338  auto variables = bfs.back().get_variable_names();
339  std::transform(
340  variables.begin(), variables.end(), std::inserter(all_inputs, all_inputs.end()), [nl](const std::string& var) { return BooleanFunctionNetDecorator::get_net_from(nl, var).get(); });
341  }
342 
343  // check whether any of the input gates have multiple outputs (not allowed for FFs)
344  for (const auto* in_gate : input_gates)
345  {
346  const auto& fan_out_nets = in_gate->get_fan_out_nets();
347  if (fan_out_nets.size() != 1)
348  {
349  log_error("hawkeye", "gate '{}' with ID {} has none or multiple fan-out nets, which is currently not supported", in_gate->get_name(), in_gate->get_id());
350  }
351  }
352 
353  // gather state inputs actually used by the analyzed component
354  const auto& state_inputs = candidate->get_state_inputs();
355  std::set<const Net*> actual_state_inputs;
356  std::set_intersection(all_inputs.begin(), all_inputs.end(), state_inputs.begin(), state_inputs.end(), std::inserter(actual_state_inputs, actual_state_inputs.begin()));
357 
358  // gather control inputs actually used by the analyzed component
359  const auto& control_inputs = candidate->get_control_inputs();
360  std::set<Net*> actual_control_inputs;
361  std::set_intersection(all_inputs.begin(), all_inputs.end(), control_inputs.begin(), control_inputs.end(), std::inserter(actual_control_inputs, actual_control_inputs.begin()));
362 
363  // gather other inputs actually used by the analyzed component
364  const auto& other_inputs = candidate->get_other_inputs();
365  std::set<Net*> actual_other_inputs;
366  std::set_intersection(all_inputs.begin(), all_inputs.end(), other_inputs.begin(), other_inputs.end(), std::inserter(actual_other_inputs, actual_other_inputs.begin()));
367 
368  // also gather unique Boolean variable names of state inputs from corresponding nets
369  std::vector<std::string> actual_state_input_names;
370  for (const auto* n : actual_state_inputs)
371  {
372  actual_state_input_names.push_back(BooleanFunctionNetDecorator(*n).get_boolean_variable_name());
373  }
374 
375  if (actual_control_inputs.size() <= 8)
376  {
377  const auto bf_const_0 = BooleanFunction::Const(0, 1);
378  const auto bf_const_1 = BooleanFunction::Const(1, 1);
379  std::vector<BooleanFunction> bf_const = {bf_const_0, bf_const_1};
380 
381  // set all other inputs to '0'
382  for (auto& bf : bfs)
383  {
384  for (const auto* other_in : actual_other_inputs)
385  {
386  const auto sub_res = bf.substitute(BooleanFunctionNetDecorator(*other_in).get_boolean_variable_name(), bf_const_0);
387  if (sub_res.is_error())
388  {
389  return ERR(sub_res.get_error());
390  }
391  bf = sub_res.get();
392  }
393  // bf = bf.simplify();
394  }
395 
396  // brute-force all control inputs
397  for (u32 i = 0; i < (1 << actual_control_inputs.size()); i++)
398  {
399  // prepare values to assign to control inputs
400  std::map<std::string, BooleanFunction> control_values;
401  u32 j = 0;
402  for (auto* ci : actual_control_inputs)
403  {
404  control_values[BooleanFunctionNetDecorator(*ci).get_boolean_variable_name()] = bf_const.at((i >> j) & 1);
405  j++;
406  }
407 
408  // actually assign the values
409  std::vector<std::vector<BooleanFunction::Value>> truth_tables_inverted(1 << actual_state_inputs.size(), std::vector<BooleanFunction::Value>(bfs.size()));
410  for (j = 0; j < bfs.size(); j++)
411  {
412  const auto& bf = bfs.at(j);
413  const auto tt_res = bf.substitute(control_values).map<std::vector<std::vector<BooleanFunction::Value>>>([&actual_state_input_names](auto&& bf) {
414  // return bf.simplify().compute_truth_table(actual_state_input_names);
415  return bf.compute_truth_table(actual_state_input_names);
416  });
417  if (tt_res.is_error())
418  {
419  return ERR(tt_res.get_error());
420  }
421 
422  auto tmp = tt_res.get().front();
423  for (u32 k = 0; k < tmp.size(); k++)
424  {
425  truth_tables_inverted.at(k).at(j) = tmp.at(k);
426  }
427  }
428 
429  std::vector<u64> sbox_tmp;
430  for (const auto& tt : truth_tables_inverted)
431  {
432  const auto u64_res = BooleanFunction::to_u64(tt);
433  if (u64_res.is_error())
434  {
435  return ERR(u64_res.get_error());
436  }
437  sbox_tmp.push_back(u64_res.get());
438  }
439 
440  // check linear independence of outputs if more outputs than inputs
441  // remove outputs that are linearly dependent on others
442  // basically uses Gauss elimination
443  if (input_gates.size() != output_gates.size())
444  {
445  std::vector<u64> mat;
446  for (j = 0; j < output_gates.size(); j++)
447  {
448  u64 sum = 0;
449  for (u32 k = 0; k < (1 << input_gates.size()); k++)
450  {
451  sum += (1 << k) * ((sbox_tmp[k] >> j) & 1);
452  }
453  mat.push_back(sum);
454  }
455 
456  for (j = 0; j < (1 << input_gates.size()); j++)
457  {
458  u32 kk = 0;
459  for (u32 k = 0; k < output_gates.size(); k++)
460  {
461  if ((mat.at(k) >> j) & 1 == 1)
462  {
463  kk = k;
464  break;
465  }
466  }
467 
468  for (u32 k = kk + 1; k < output_gates.size(); k++)
469  {
470  if ((mat.at(k) >> j) & 1 == 1)
471  {
472  mat.at(k) = mat.at(k) ^ mat.at(kk);
473  }
474  }
475  }
476 
477  std::vector<u32> idx;
478  for (j = 0; j < mat.size(); j++)
479  {
480  if (mat.at(j) != 0)
481  {
482  idx.push_back(j);
483  }
484  }
485 
486  if (idx.size() == input_gates.size())
487  {
488  for (j = 0; j < sbox_tmp.size(); j++)
489  {
490  u8 new_val = 0;
491  for (u32 k = 0; k < idx.size(); k++)
492  {
493  new_val += (1 << k) * ((sbox_tmp.at(j) >> idx.at(k)) & 1);
494  }
495  sbox_tmp.at(j) = new_val;
496  }
497  }
498  else
499  {
500  log_info("hawkeye", "found {} linear independent", idx.size());
501  continue;
502  }
503  }
504 
505  std::vector<u8> sbox;
506  for (const auto elem : sbox_tmp)
507  {
508  sbox.push_back((u8)elem);
509  }
510 
511  std::set<u8> sbox_set(sbox.begin(), sbox.end());
512  if (sbox.size() != sbox_set.size())
513  {
514  log_info("hawkeye", "found non-bijective S-box");
515  continue;
516  }
517 
518  if (const auto sbox_res = db.lookup(sbox); sbox_res.is_ok())
519  {
520  sbox_name = sbox_res.get();
521  break;
522  }
523  }
524  }
525 
526  auto duration_in_seconds = std::chrono::duration<double>(std::chrono::system_clock::now() - start).count();
527  if (!sbox_name.empty())
528  {
529  log_info("hawkeye", "identified {} S-box in {:.2f} seconds", sbox_name, duration_in_seconds);
530  }
531  else
532  {
533  log_info("hawkeye", "could not identify S-box in {:.2f} seconds", duration_in_seconds);
534  }
535 
536  return OK(sbox_name);
537  }
538  } // namespace hawkeye
539 } // namespace hal
std::set< u32 > in_reg
static BooleanFunction Const(const BooleanFunction::Value &value)
static Result< u64 > to_u64(const std::vector< BooleanFunction::Value > &value)
Definition: gate.h:58
A round candidate constructed from a previously discovered register candidate.
const std::set< Net * > & get_other_inputs() const
Get the candidate's other inputs to the logic computing the next state.
const std::set< Net * > & get_control_inputs() const
Get the candidate's control inputs to the logic computing the next state.
const std::map< Gate *, std::set< Gate * > > & get_input_ffs_of_gate() const
Get a map from each combinational gate of the round function to all the input flip-flops it depends o...
const std::map< u32, std::set< Gate * > > & get_longest_distance_to_gate() const
Get a map from an integer distance to all gates that are reachable within at most that distance when ...
const std::set< Gate * > & get_output_reg() const
Get the candidate's output register.
Netlist * get_netlist() const
Get the netlist of the round candidate. The netlist is a partial copy of the netlist of the register ...
graph_algorithm::NetlistGraph * get_graph() const
Get the netlist graph of the round candidate.
const std::set< Gate * > & get_input_reg() const
Get the candidate's input register.
const std::set< Net * > & get_state_inputs() const
Get the candidate's state inputs to the logic computing the next state.
An S-box candidate discovered within the round function of a round candidate.
Definition: sbox_lookup.h:58
std::set< Gate * > m_output_gates
The output gates of the S-box candidate (usually combinational logic that is input to the linear laye...
Definition: sbox_lookup.h:88
const RoundCandidate * m_candidate
The RoundCandidate that the S-box candidate belongs to.
Definition: sbox_lookup.h:73
std::vector< Gate * > m_component
The gates of the component which the S-box candidate is part of.
Definition: sbox_lookup.h:78
std::set< Gate * > m_input_gates
The input gates of the S-box candidate (will be flip-flops).
Definition: sbox_lookup.h:83
Database of known S-boxes.
Definition: sbox_database.h:50
Result< std::string > lookup(const std::vector< u8 > &sbox) const
Attempt to look up an S-box in the database.
This file contains functions related to graph components.
uint64_t u64
Definition: defines.h:42
uint32_t u32
Definition: defines.h:41
uint8_t u8
Definition: defines.h:39
#define log_error(channel,...)
Definition: log.h:78
#define log_info(channel,...)
Definition: log.h:70
#define ERR(message)
Definition: result.h:60
#define OK(...)
Definition: result.h:56
Result< std::vector< std::vector< u32 > > > get_connected_components(const NetlistGraph *graph, bool strong, u32 min_size=0)
Compute the (strongly) connected components of the specified graph.
Definition: components.cpp:11
Result< std::unique_ptr< NetlistGraph > > get_subgraph(const NetlistGraph *graph, const std::vector< Gate * > &subgraph_gates)
Compute the subgraph induced by the specified gates, including all edges between the corresponding ve...
Definition: subgraph.cpp:10
Result< std::vector< SBoxCandidate > > locate_sboxes(const RoundCandidate *candidate)
Try to locate S-box candidates within the combinational next-state logic of the round function candid...
Definition: sbox_lookup.cpp:18
Result< std::string > identify_sbox(const SBoxCandidate &sbox_candidate, const SBoxDatabase &db)
Try to identify an S-box candidate by matching it against a database of known S-boxes under affine eq...
Definition: defines.h:45
This file contains the class that holds all information on a round candidate.
This file contains a class that holds all information on an S-box candidate as well as the functions ...
This file contains functions related to subgraphs.