|
1 | 1 | #!/usr/bin/env ruby
|
2 |
| -# $Id$ |
3 |
| -# $Revision$ |
4 | 2 |
|
5 | 3 | msfbase = __FILE__
|
6 | 4 | while File.symlink?(msfbase)
|
7 | 5 | msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
|
8 | 6 | end
|
9 | 7 |
|
10 |
| -$:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', '..', 'lib'))) |
11 |
| -require 'msfenv' |
12 |
| - |
13 |
| -$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] |
14 |
| - |
15 |
| -require 'rex' |
16 |
| - |
17 |
| -if ARGV.length < 1 |
18 |
| - $stderr.puts("Usage: #{File.basename($0)} <search item> <length of buffer>") |
19 |
| - $stderr.puts("Default length of buffer if none is inserted: 8192") |
20 |
| - $stderr.puts("This buffer is generated by pattern_create() in the Rex library automatically") |
21 |
| - exit |
22 |
| -end |
23 |
| - |
24 |
| -value = ARGV.shift |
25 |
| -len = ARGV.shift || 8192 |
26 |
| - |
27 |
| -=begin |
| 8 | +$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', '..', 'lib'))) |
| 9 | +$LOAD_PATH.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] |
28 | 10 |
|
29 |
| -Examples: |
30 |
| -
|
31 |
| -$ ./tools/pattern_create.rb 128 |
32 |
| -Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae |
33 |
| -
|
34 |
| -$ ./tools/pattern_offset.rb 8Ac9 |
35 |
| -[*] Exact match at offset 86 |
36 |
| -
|
37 |
| -$ ./tools/pattern_offset.rb 39634138 |
38 |
| -[*] Exact match at offset 86 |
39 |
| -
|
40 |
| -$ ./tools/pattern_offset.rb 0x39634138 |
41 |
| -[*] Exact match at offset 86 |
42 |
| -
|
43 |
| -$ ./tools/pattern_offset.rb 0x396341FF |
44 |
| -[*] No exact matches, looking for likely candidates... |
45 |
| -[+] Possible match at offset 86 (adjusted [ little-endian: 199 | big-endian: 18996934 ] ) byte offset 0 |
| 11 | +require 'msfenv' |
| 12 | +require 'msf/core' |
| 13 | +require 'msf/base' |
| 14 | +require 'rex/text' |
| 15 | +require 'optparse' |
| 16 | + |
| 17 | +module PatternOffset |
| 18 | + class OptsConsole |
| 19 | + def self.parse(args) |
| 20 | + options = {} |
| 21 | + parser = OptionParser.new do |opt| |
| 22 | + opt.banner = "Usage: #{__FILE__} [options]\nExample: #{__FILE__} -q Aa3A|0x39634138|0xFFFF4138\n[*] Exact match at offset 9" |
| 23 | + opt.separator '' |
| 24 | + opt.separator 'Options:' |
| 25 | + |
| 26 | + opt.on('-q', '--query Aa0A', String, "Query to Locate") do |query| |
| 27 | + options[:query] = query |
| 28 | + end |
| 29 | + |
| 30 | + opt.on('-l', '--length <length>', Integer, "The length of the pattern") do |len| |
| 31 | + options[:length] = len |
| 32 | + end |
| 33 | + |
| 34 | + opt.on('-s', '--sets <ABC,def,123>', Array, "Custom Pattern Sets") do |sets| |
| 35 | + options[:sets] = sets |
| 36 | + end |
| 37 | + |
| 38 | + opt.on_tail('-h', '--help', 'Show this message') do |
| 39 | + $stdout.puts opt |
| 40 | + exit |
| 41 | + end |
| 42 | + end |
46 | 43 |
|
47 |
| -$ ./tools/pattern_offset.rb 0x3963FFFF |
48 |
| -[*] No exact matches, looking for likely candidates... |
49 |
| -[+] Possible match at offset 86 (adjusted [ little-endian: 48839 | big-endian: 19045574 ] ) |
50 |
| -[ snip ] |
| 44 | + parser.parse!(args) |
51 | 45 |
|
52 |
| -$ ./tools/pattern_offset.rb 0xFFFF4138 |
53 |
| -[*] No exact matches, looking for likely candidates... |
54 |
| -[+] Possible match at offset 26 (adjusted [ little-endian: 3332243456 | big-endian: 3351109631 ] ) |
55 |
| -[+] Possible match at offset 56 (adjusted [ little-endian: 3332177920 | big-endian: 3351109375 ] ) |
56 |
| -[+] Possible match at offset 86 (adjusted [ little-endian: 3332112384 | big-endian: 3351109119 ] ) |
57 |
| -[ snip ] |
| 46 | + if options.empty? |
| 47 | + raise OptionParser::MissingArgument, 'No options set, try -h for usage' |
| 48 | + elsif options[:query].blank? |
| 49 | + raise OptionParser::MissingArgument, '-q <query> is required' |
| 50 | + elsif options[:length].blank? && options[:sets] |
| 51 | + raise OptionParser::MissingArgument, '-l <length> is required' |
| 52 | + end |
58 | 53 |
|
59 |
| -=end |
| 54 | + options[:sets] = nil unless options[:sets] |
| 55 | + options[:length] = 1024 unless options[:length] |
60 | 56 |
|
| 57 | + options |
| 58 | + end |
| 59 | + end |
61 | 60 |
|
| 61 | + class Driver |
| 62 | + def initialize |
| 63 | + begin |
| 64 | + @opts = OptsConsole.parse(ARGV) |
| 65 | + rescue OptionParser::ParseError => e |
| 66 | + $stderr.puts "[x] #{e.message}" |
| 67 | + exit |
| 68 | + end |
| 69 | + end |
62 | 70 |
|
63 |
| -# The normal format is a full hexadecimal value: 0x41424344 |
64 |
| -if (value.length >= 8 and value.hex > 0) |
65 |
| - value = value.hex |
66 |
| -# However, you can also specify a four-byte string |
67 |
| -elsif (value.length == 4) |
68 |
| - value = value.unpack("V").first |
69 |
| -else |
70 |
| -# Or even a hex value that isn't 8 bytes long |
71 |
| - value = value.to_i(16) |
72 |
| -end |
| 71 | + def run |
| 72 | + query = (@opts[:query]) |
| 73 | + |
| 74 | + if query.length >= 8 && query.hex > 0 |
| 75 | + query = query.hex |
| 76 | + # However, you can also specify a four-byte string |
| 77 | + elsif query.length == 4 |
| 78 | + query = query.unpack("V").first |
| 79 | + else |
| 80 | + # Or even a hex query that isn't 8 bytes long |
| 81 | + query = query.to_i(16) |
| 82 | + end |
73 | 83 |
|
74 |
| -buffer = Rex::Text.pattern_create(len.to_i) |
75 |
| -offset = Rex::Text.pattern_offset(buffer, value) |
76 |
| - |
77 |
| -# Handle cases where there is no match by looking for "close" matches |
78 |
| -unless offset |
79 |
| - found = false |
80 |
| - $stderr.puts "[*] No exact matches, looking for likely candidates..." |
81 |
| - |
82 |
| - # Look for shifts by a single byte |
83 |
| - 0.upto(3) do |idx| |
84 |
| - 0.upto(255) do |c| |
85 |
| - nvb = [value].pack("V") |
86 |
| - nvb[idx, 1] = [c].pack("C") |
87 |
| - nvi = nvb.unpack("V").first |
88 |
| - |
89 |
| - off = Rex::Text.pattern_offset(buffer, nvi) |
90 |
| - if off |
91 |
| - mle = value - buffer[off,4].unpack("V").first |
92 |
| - mbe = value - buffer[off,4].unpack("N").first |
93 |
| - puts "[+] Possible match at offset #{off} (adjusted [ little-endian: #{mle} | big-endian: #{mbe} ] ) byte offset #{idx}" |
94 |
| - found = true |
| 84 | + buffer = Rex::Text.pattern_create(@opts[:length], @opts[:sets]) |
| 85 | + offset = Rex::Text.pattern_offset(buffer, query) |
| 86 | + |
| 87 | + # Handle cases where there is no match by looking for "close" matches |
| 88 | + unless offset |
| 89 | + found = false |
| 90 | + $stderr.puts "[*] No exact matches, looking for likely candidates..." |
| 91 | + |
| 92 | + # Look for shifts by a single byte |
| 93 | + 0.upto(3) do |idx| |
| 94 | + 0.upto(255) do |c| |
| 95 | + nvb = [query].pack("V") |
| 96 | + nvb[idx, 1] = [c].pack("C") |
| 97 | + nvi = nvb.unpack("V").first |
| 98 | + |
| 99 | + off = Rex::Text.pattern_offset(buffer, nvi) |
| 100 | + if off |
| 101 | + mle = query - buffer[off, 4].unpack("V").first |
| 102 | + mbe = query - buffer[off, 4].unpack("N").first |
| 103 | + puts "[+] Possible match at offset #{off} (adjusted [ little-endian: #{mle} | big-endian: #{mbe} ] ) byte offset #{idx}" |
| 104 | + found = true |
| 105 | + end |
| 106 | + end |
| 107 | + end |
| 108 | + |
| 109 | + exit! if found |
| 110 | + |
| 111 | + # Look for 16-bit offsets |
| 112 | + [0, 2].each do |idx| |
| 113 | + 0.upto(65535) do |c| |
| 114 | + nvb = [query].pack("V") |
| 115 | + nvb[idx, 2] = [c].pack("v") |
| 116 | + nvi = nvb.unpack("V").first |
| 117 | + |
| 118 | + off = Rex::Text.pattern_offset(buffer, nvi) |
| 119 | + if off |
| 120 | + mle = query - buffer[off, 4].unpack("V").first |
| 121 | + mbe = query - buffer[off, 4].unpack("N").first |
| 122 | + puts "[+] Possible match at offset #{off} (adjusted [ little-endian: #{mle} | big-endian: #{mbe} ] )" |
| 123 | + found = true |
| 124 | + end |
| 125 | + end |
| 126 | + end |
95 | 127 | end
|
96 |
| - end |
97 |
| - end |
98 | 128 |
|
99 |
| - exit if found |
100 |
| - |
101 |
| - # Look for 16-bit offsets |
102 |
| - [0, 2].each do |idx| |
103 |
| - 0.upto(65535) do |c| |
104 |
| - nvb = [value].pack("V") |
105 |
| - nvb[idx, 2] = [c].pack("v") |
106 |
| - nvi = nvb.unpack("V").first |
107 |
| - |
108 |
| - off = Rex::Text.pattern_offset(buffer, nvi) |
109 |
| - if off |
110 |
| - mle = value - buffer[off,4].unpack("V").first |
111 |
| - mbe = value - buffer[off,4].unpack("N").first |
112 |
| - puts "[+] Possible match at offset #{off} (adjusted [ little-endian: #{mle} | big-endian: #{mbe} ] )" |
113 |
| - found = true |
| 129 | + while offset |
| 130 | + puts "[*] Exact match at offset #{offset}" |
| 131 | + offset = Rex::Text.pattern_offset(buffer, query, offset + 1) |
114 | 132 | end
|
115 | 133 | end
|
116 | 134 | end
|
117 |
| - |
118 | 135 | end
|
119 | 136 |
|
120 |
| -while offset |
121 |
| - puts "[*] Exact match at offset #{offset}" |
122 |
| - offset = Rex::Text.pattern_offset(buffer, value, offset + 1) |
| 137 | +if __FILE__ == $PROGRAM_NAME |
| 138 | + driver = PatternOffset::Driver.new |
| 139 | + begin |
| 140 | + driver.run |
| 141 | + rescue ::StandardError => e |
| 142 | + elog("#{e.class}: #{e.message}\n#{e.backtrace * "\n"}") |
| 143 | + $stderr.puts "[x] #{e.class}: #{e.message}" |
| 144 | + $stderr.puts "[*] If necessary, please refer to framework.log for more details." |
| 145 | + |
| 146 | + end |
123 | 147 | end
|
0 commit comments