CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/payloads/shuffle.rb
Views: 11623
1
# -*- coding: binary -*-
2
3
require 'rex/arch'
4
##
5
# This module contains a helper function for generating payloads from a shuffled
6
# set of instructions loaded from a special file.
7
##
8
module Rex
9
module Payloads
10
class Shuffle
11
12
FLOW_INSTRUCTIONS = {}
13
FLOW_INSTRUCTIONS[Rex::Arch::ARCH_X86] = %w{ call jae jb jbe jc jcxz je jecxz jg jge jl jle jmp jna jnae jnb jnbe jnc jne jng jnge jnl jnle jno jnp jns jnz jo jp jpe jpo js jz }.freeze
14
FLOW_INSTRUCTIONS[Rex::Arch::ARCH_X64] = (FLOW_INSTRUCTIONS[Rex::Arch::ARCH_X86] + %w{ jrcxz }).freeze
15
private_constant :FLOW_INSTRUCTIONS
16
17
#
18
# Shuffle instructions from a GraphML data file and return the assembly source. If an architecture is specified
19
# and supported, labels will be added for control flow instructions such as jumps and calls. Labels are necessary
20
# if any post processing is performed on the source (such as for obfuscation).
21
#
22
# @param file_path [String] The file path to load the GraphML data from.
23
# @param name [String] An optional symbol name to apply to the assembly source.
24
def self.from_graphml_file(file_path, arch: nil, name: nil)
25
graphml = Rex::Parser::GraphML.from_file(file_path)
26
blocks = create_path(graphml.nodes.select { |_id,node| node.attributes['type'] == 'block' }, graphml.graphs[0].edges)
27
blocks.map! { |block| { node: block, instructions: process_block(block) } }
28
29
label_prefix = Rex::Text.rand_text_alpha_lower(4)
30
labeler = lambda { |address| "loc_#{label_prefix}#{ address.to_s(16).rjust(4, '0') }" }
31
32
source_lines = []
33
labeled = []
34
label_refs = []
35
blocks.each do |block|
36
if [ Rex::Arch::ARCH_X86, Rex::Arch::ARCH_X64 ].include? arch
37
source_lines << labeler.call(block[:node].attributes['address']) + ':'
38
labeled << block[:node].attributes['address']
39
# by default use the raw binary instruction to avoid syntax compatibility issues with metasm
40
instructions = block[:instructions].map { |node| 'db ' + node.attributes['instruction.hex'].strip.chars.each_slice(2).map { |hex| '0x' + hex.join }.join(', ') }
41
else
42
instructions = block[:instructions].map { |node| node.attributes['instruction.source'] }
43
end
44
unless arch.nil?
45
raise ArgumentError, 'Unsupported architecture' if FLOW_INSTRUCTIONS[arch].nil?
46
47
# if a supported architecture was specified, use the original source and apply the necessary labels
48
block[:instructions].each_with_index do |node, index|
49
next unless match = /^(?<mnemonic>\S+)\s+(?<address>0x[a-f0-9]+)$/.match(node.attributes['instruction.source'])
50
next unless FLOW_INSTRUCTIONS[arch].include? match[:mnemonic]
51
52
address = Integer(match[:address])
53
instructions[index] = "#{match[:mnemonic]} #{labeler.call(address)}"
54
label_refs << address
55
end
56
end
57
58
source_lines += instructions
59
end
60
61
unless label_refs.all? { |address| labeled.include? address }
62
# raise this here so it's closer to the source of the problem :(
63
raise StandardError, 'Missing label reference'
64
end
65
66
67
source_lines = ([name + ':'] + source_lines.map { |source_line| ' ' + source_line}) unless name.nil?
68
source_lines.join("\n") + "\n"
69
end
70
71
class << self
72
private
73
74
#
75
# Process the specified graph element which represents a single basic block in assembly. This graph element
76
# contains nodes representing each of its instructions.
77
#
78
def process_block(block)
79
subgraph = block.subgraph
80
instructions = subgraph.nodes.select { |_id, node| node.attributes['type'] == 'instruction' }
81
create_path(instructions, subgraph.edges)
82
end
83
84
def create_path(nodes, edges)
85
path = []
86
87
# the initial choices are any node without a predecessor (dependency)
88
targets = edges.map(&:target)
89
choices = nodes.values.select { |node| !targets.include? node.id }
90
until choices.empty?
91
selection = choices.sample
92
choices.delete(selection)
93
path << selection
94
95
# check each node for which the selection is a dependency
96
successors = selection.target_edges.map { |edge| nodes[edge.target] }
97
successors.each do |successor|
98
next if path.include? successor
99
next if !successor.source_edges.map { |edge| path.include? nodes[edge.source] }.all?
100
101
choices << successor
102
end
103
end
104
105
path
106
end
107
end
108
end
109
end
110
end
111
112