rex-bin_tools 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +1 -0
- data.tar.gz.sig +0 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +52 -0
- data/Gemfile +4 -0
- data/LICENSE +27 -0
- data/README.md +22 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/msfbinscan +284 -0
- data/bin/msfelfscan +120 -0
- data/bin/msfmachscan +100 -0
- data/bin/msfpescan +184 -0
- data/bin/setup +8 -0
- data/data/identify.txt +3043 -0
- data/lib/rex/assembly/nasm.rb +104 -0
- data/lib/rex/bin_tools.rb +13 -0
- data/lib/rex/bin_tools/version.rb +5 -0
- data/lib/rex/elfparsey.rb +9 -0
- data/lib/rex/elfparsey/elf.rb +121 -0
- data/lib/rex/elfparsey/elfbase.rb +265 -0
- data/lib/rex/elfparsey/exceptions.rb +25 -0
- data/lib/rex/elfscan.rb +10 -0
- data/lib/rex/elfscan/scanner.rb +226 -0
- data/lib/rex/elfscan/search.rb +44 -0
- data/lib/rex/image_source.rb +10 -0
- data/lib/rex/image_source/disk.rb +58 -0
- data/lib/rex/image_source/image_source.rb +48 -0
- data/lib/rex/image_source/memory.rb +35 -0
- data/lib/rex/machparsey.rb +9 -0
- data/lib/rex/machparsey/exceptions.rb +31 -0
- data/lib/rex/machparsey/mach.rb +209 -0
- data/lib/rex/machparsey/machbase.rb +408 -0
- data/lib/rex/machscan.rb +9 -0
- data/lib/rex/machscan/scanner.rb +217 -0
- data/lib/rex/peparsey.rb +10 -0
- data/lib/rex/peparsey/exceptions.rb +30 -0
- data/lib/rex/peparsey/pe.rb +210 -0
- data/lib/rex/peparsey/pe_memdump.rb +61 -0
- data/lib/rex/peparsey/pebase.rb +1662 -0
- data/lib/rex/peparsey/section.rb +128 -0
- data/lib/rex/pescan.rb +11 -0
- data/lib/rex/pescan/analyze.rb +366 -0
- data/lib/rex/pescan/scanner.rb +230 -0
- data/lib/rex/pescan/search.rb +68 -0
- data/rex-bin_tools.gemspec +32 -0
- metadata +284 -0
- metadata.gz.sig +0 -0
data/lib/rex/machscan.rb
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
|
3
|
+
module Rex
|
4
|
+
module MachScan
|
5
|
+
module Scanner
|
6
|
+
class Generic
|
7
|
+
|
8
|
+
attr_accessor :mach, :fat, :regex
|
9
|
+
|
10
|
+
def initialize(binary)
|
11
|
+
if binary.class == Rex::MachParsey::Mach
|
12
|
+
self.mach = binary
|
13
|
+
else
|
14
|
+
self.fat = binary
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def config(param)
|
19
|
+
end
|
20
|
+
|
21
|
+
def scan(param)
|
22
|
+
config(param)
|
23
|
+
|
24
|
+
$stdout.puts "[#{param['file']}]"
|
25
|
+
|
26
|
+
if !self.mach
|
27
|
+
for mach in fat.machos
|
28
|
+
if mach.mach_header.cputype == 0x7 #since we only support intel for the time being its all we process
|
29
|
+
self.mach = mach
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
self.mach.segments.each do |segment|
|
35
|
+
if segment.segname.include? "__TEXT"
|
36
|
+
scan_segment(segment, param).each do |hit|
|
37
|
+
vaddr = hit[0]
|
38
|
+
message = hit[1].is_a?(Array) ? hit[1].join(" ") : hit[1]
|
39
|
+
$stdout.puts self.mach.ptr_s(vaddr - self.mach.fat_offset) + " " + message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def scan_segment(segment, param={})
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class JmpRegScanner < Generic
|
52
|
+
|
53
|
+
def config(param)
|
54
|
+
regnums = param['args']
|
55
|
+
|
56
|
+
# build a list of the call bytes
|
57
|
+
calls = _build_byte_list(0xd0, regnums - [4]) # note call esp's don't work..
|
58
|
+
jmps = _build_byte_list(0xe0, regnums)
|
59
|
+
pushs1 = _build_byte_list(0x50, regnums)
|
60
|
+
pushs2 = _build_byte_list(0xf0, regnums)
|
61
|
+
|
62
|
+
regexstr = '('
|
63
|
+
if !calls.empty?
|
64
|
+
regexstr += "\xff[#{calls}]|"
|
65
|
+
end
|
66
|
+
|
67
|
+
regexstr += "\xff[#{jmps}]|([#{pushs1}]|\xff[#{pushs2}])(\xc3|\xc2..))"
|
68
|
+
|
69
|
+
self.regex = Regexp.new(regexstr, nil, 'n')
|
70
|
+
end
|
71
|
+
|
72
|
+
# build a list for regex of the possible bytes, based on a base
|
73
|
+
# byte and a list of register numbers..
|
74
|
+
def _build_byte_list(base, regnums)
|
75
|
+
regnums.collect { |regnum| Regexp.escape((base | regnum).chr) }.join('')
|
76
|
+
end
|
77
|
+
|
78
|
+
def _ret_size(offset)
|
79
|
+
case mach.read(offset, 1)
|
80
|
+
when "\xc3"
|
81
|
+
return 1
|
82
|
+
when "\xc2"
|
83
|
+
return 3
|
84
|
+
end
|
85
|
+
$stderr.puts("Invalid return instruction")
|
86
|
+
end
|
87
|
+
|
88
|
+
def _parse_ret(data)
|
89
|
+
if data.length == 1
|
90
|
+
return "ret"
|
91
|
+
else
|
92
|
+
return "retn 0x%04x" % data[1, 2].unpack('v')[0]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def scan_segment(segment, param={})
|
97
|
+
base_addr = segment.vmaddr
|
98
|
+
segment_offset = segment.fileoff
|
99
|
+
offset = segment_offset
|
100
|
+
|
101
|
+
hits = []
|
102
|
+
|
103
|
+
while (offset = mach.index(regex, offset)) != nil
|
104
|
+
|
105
|
+
vaddr = base_addr + (offset - segment_offset)
|
106
|
+
message = ''
|
107
|
+
|
108
|
+
parse_ret = false
|
109
|
+
|
110
|
+
byte1 = mach.read(offset, 1).unpack("C*")[0]
|
111
|
+
|
112
|
+
if byte1 == 0xff
|
113
|
+
byte2 = mach.read(offset+1, 1).unpack("C*")[0]
|
114
|
+
regname = Rex::Arch::X86.reg_name32(byte2 & 0x7)
|
115
|
+
|
116
|
+
case byte2 & 0xf8
|
117
|
+
when 0xd0
|
118
|
+
message = "call #{regname}"
|
119
|
+
offset += 2
|
120
|
+
when 0xe0
|
121
|
+
message = "jmp #{regname}"
|
122
|
+
offset += 2
|
123
|
+
when 0xf0
|
124
|
+
retsize = _ret_size(offset+2)
|
125
|
+
message = "push #{regname}; " + _parse_ret(mach.read(offset+2, retsize))
|
126
|
+
offset += 2 + retsize
|
127
|
+
else
|
128
|
+
raise "Unexpected value at offset: #{offset}"
|
129
|
+
end
|
130
|
+
else
|
131
|
+
regname = Rex::Arch::X86.reg_name32(byte1 & 0x7)
|
132
|
+
retsize = _ret_size(offset+1)
|
133
|
+
message = "push #{regname}; " + _parse_ret(mach.read(offset+1, retsize))
|
134
|
+
offset += 1 + retsize
|
135
|
+
end
|
136
|
+
|
137
|
+
hits << [ vaddr, message ]
|
138
|
+
end
|
139
|
+
|
140
|
+
return hits
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class PopPopRetScanner < JmpRegScanner
|
145
|
+
|
146
|
+
def config(param)
|
147
|
+
pops = _build_byte_list(0x58, (0 .. 7).to_a - [4]) # we don't want pop esp's...
|
148
|
+
self.regex = Regexp.new("[#{pops}][#{pops}](\xc3|\xc2..)", nil, 'n')
|
149
|
+
end
|
150
|
+
|
151
|
+
def scan_segment(segment, param={})
|
152
|
+
base_addr = segment.vmaddr
|
153
|
+
segment_offset = segment.fileoff
|
154
|
+
offset = segment_offset
|
155
|
+
|
156
|
+
hits = []
|
157
|
+
|
158
|
+
while offset < segment.fileoff + segment.filesize && (offset = mach.index(regex, offset)) != nil
|
159
|
+
|
160
|
+
vaddr = base_addr + (offset - segment_offset)
|
161
|
+
message = ''
|
162
|
+
|
163
|
+
pops = mach.read(offset, 2)
|
164
|
+
reg1 = Rex::Arch::X86.reg_name32(pops[0,1].unpack("C*")[0] & 0x7)
|
165
|
+
reg2 = Rex::Arch::X86.reg_name32(pops[1,1].unpack("C*")[0] & 0x7)
|
166
|
+
|
167
|
+
message = "pop #{reg1}; pop #{reg2}; "
|
168
|
+
|
169
|
+
retsize = _ret_size(offset+2)
|
170
|
+
message += _parse_ret(mach.read(offset+2, retsize))
|
171
|
+
|
172
|
+
offset += 2 + retsize
|
173
|
+
|
174
|
+
hits << [ vaddr, message ]
|
175
|
+
end
|
176
|
+
|
177
|
+
return hits
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class RegexScanner < JmpRegScanner
|
182
|
+
|
183
|
+
def config(param)
|
184
|
+
self.regex = Regexp.new(param['args'], nil, 'n')
|
185
|
+
end
|
186
|
+
|
187
|
+
def scan_segment(segment, param={})
|
188
|
+
base_addr = segment.vmaddr
|
189
|
+
segment_offset = segment.fileoff
|
190
|
+
offset = segment_offset
|
191
|
+
|
192
|
+
hits = []
|
193
|
+
|
194
|
+
while offset < segment.fileoff + segment.filesize && (offset = mach.index(regex, offset)) != nil
|
195
|
+
|
196
|
+
idx = offset
|
197
|
+
buf = ''
|
198
|
+
mat = nil
|
199
|
+
|
200
|
+
while (! (mat = buf.match(regex)))
|
201
|
+
buf << mach.read(idx, 1)
|
202
|
+
idx += 1
|
203
|
+
end
|
204
|
+
|
205
|
+
vaddr = base_addr + (offset - segment_offset)
|
206
|
+
|
207
|
+
hits << [ vaddr, buf.unpack("H*") ]
|
208
|
+
offset += buf.length
|
209
|
+
end
|
210
|
+
return hits
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
data/lib/rex/peparsey.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
|
3
|
+
module Rex
|
4
|
+
module PeParsey
|
5
|
+
|
6
|
+
class PeError < ::RuntimeError
|
7
|
+
end
|
8
|
+
|
9
|
+
class ParseError < PeError
|
10
|
+
end
|
11
|
+
|
12
|
+
class DosHeaderError < ParseError
|
13
|
+
end
|
14
|
+
|
15
|
+
class FileHeaderError < ParseError
|
16
|
+
end
|
17
|
+
|
18
|
+
class OptionalHeaderError < ParseError
|
19
|
+
end
|
20
|
+
|
21
|
+
class BoundsError < PeError
|
22
|
+
end
|
23
|
+
|
24
|
+
class PeParseyError < PeError
|
25
|
+
end
|
26
|
+
|
27
|
+
class SkipError < PeError
|
28
|
+
end
|
29
|
+
|
30
|
+
end end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
|
3
|
+
require 'rex/image_source'
|
4
|
+
require 'rex/peparsey/exceptions'
|
5
|
+
require 'rex/peparsey/pebase'
|
6
|
+
require 'rex/peparsey/section'
|
7
|
+
require 'rex/struct2'
|
8
|
+
|
9
|
+
module Rex
|
10
|
+
module PeParsey
|
11
|
+
class Pe < PeBase
|
12
|
+
|
13
|
+
def initialize(isource)
|
14
|
+
|
15
|
+
#
|
16
|
+
# DOS Header
|
17
|
+
#
|
18
|
+
# Parse the initial dos header, starting at the file beginning
|
19
|
+
#
|
20
|
+
offset = 0
|
21
|
+
dos_header = self.class._parse_dos_header(isource.read(offset, IMAGE_DOS_HEADER_SIZE))
|
22
|
+
|
23
|
+
#
|
24
|
+
# File Header
|
25
|
+
#
|
26
|
+
# If there is going to be a PE, the dos header tells us where to find it
|
27
|
+
# So now we try to parse the file (pe) header
|
28
|
+
#
|
29
|
+
offset += dos_header.e_lfanew
|
30
|
+
|
31
|
+
# most likely an invalid e_lfanew...
|
32
|
+
if offset > isource.size
|
33
|
+
raise FileHeaderError, "e_lfanew looks invalid", caller
|
34
|
+
end
|
35
|
+
|
36
|
+
file_header = self.class._parse_file_header(isource.read(offset, IMAGE_FILE_HEADER_SIZE))
|
37
|
+
|
38
|
+
#
|
39
|
+
# Optional Header
|
40
|
+
#
|
41
|
+
# After the file header, we find the optional header. Right now
|
42
|
+
# we require a optional header. Despite it's name, all binaries
|
43
|
+
# that we are interested in should have one. We need this
|
44
|
+
# header for a lot of stuff, so we die without it...
|
45
|
+
#
|
46
|
+
offset += IMAGE_FILE_HEADER_SIZE
|
47
|
+
optional_header = self.class._parse_optional_header(
|
48
|
+
isource.read(offset, file_header.SizeOfOptionalHeader)
|
49
|
+
)
|
50
|
+
|
51
|
+
if !optional_header
|
52
|
+
raise OptionalHeaderError, "No optional header!", caller
|
53
|
+
end
|
54
|
+
|
55
|
+
base = optional_header.ImageBase
|
56
|
+
|
57
|
+
#
|
58
|
+
# Section Headers
|
59
|
+
#
|
60
|
+
# After the optional header should be the section headers.
|
61
|
+
# We know how many there should be from the file header...
|
62
|
+
#
|
63
|
+
offset += file_header.SizeOfOptionalHeader
|
64
|
+
|
65
|
+
num_sections = file_header.NumberOfSections
|
66
|
+
section_headers = self.class._parse_section_headers(
|
67
|
+
isource.read(offset, IMAGE_SIZEOF_SECTION_HEADER * num_sections)
|
68
|
+
)
|
69
|
+
|
70
|
+
#
|
71
|
+
# End of Headers
|
72
|
+
#
|
73
|
+
# After the section headers (which are padded to FileAlignment)
|
74
|
+
# we should find the section data, described by the section
|
75
|
+
# headers...
|
76
|
+
#
|
77
|
+
# So this is the end of our header data, lets store this
|
78
|
+
# in an image source for possible access later...
|
79
|
+
#
|
80
|
+
offset += IMAGE_SIZEOF_SECTION_HEADER * num_sections
|
81
|
+
offset = self.class._align_offset(offset, optional_header.FileAlignment)
|
82
|
+
|
83
|
+
header_section = Section.new(isource.subsource(0, offset), 0, nil)
|
84
|
+
|
85
|
+
#
|
86
|
+
# Sections
|
87
|
+
#
|
88
|
+
# So from here on out should be section data, and then any
|
89
|
+
# trailing data (like authenticode and stuff I think)
|
90
|
+
#
|
91
|
+
|
92
|
+
sections = [ ]
|
93
|
+
|
94
|
+
section_headers.each do |section_header|
|
95
|
+
|
96
|
+
rva = section_header.VirtualAddress
|
97
|
+
size = section_header.SizeOfRawData
|
98
|
+
file_offset = section_header.PointerToRawData
|
99
|
+
|
100
|
+
sections << Section.new(
|
101
|
+
isource.subsource(file_offset, size),
|
102
|
+
rva,
|
103
|
+
section_header
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
#
|
110
|
+
# Save the stuffs!
|
111
|
+
#
|
112
|
+
# We have parsed enough to load the file up here, now we just
|
113
|
+
# save off all of the structures and data... We will
|
114
|
+
# save our fake header section, the real sections, etc.
|
115
|
+
#
|
116
|
+
|
117
|
+
#
|
118
|
+
# These should not be accessed directly
|
119
|
+
#
|
120
|
+
|
121
|
+
self._isource = isource
|
122
|
+
|
123
|
+
self._dos_header = dos_header
|
124
|
+
self._file_header = file_header
|
125
|
+
self._optional_header = optional_header
|
126
|
+
self._section_headers = section_headers
|
127
|
+
|
128
|
+
self.image_base = base
|
129
|
+
self.sections = sections
|
130
|
+
self.header_section = header_section
|
131
|
+
|
132
|
+
self._config_header = _parse_config_header()
|
133
|
+
self._tls_header = _parse_tls_header()
|
134
|
+
|
135
|
+
# These can be accessed directly
|
136
|
+
self.hdr = HeaderAccessor.new
|
137
|
+
self.hdr.dos = self._dos_header
|
138
|
+
self.hdr.file = self._file_header
|
139
|
+
self.hdr.opt = self._optional_header
|
140
|
+
self.hdr.sections = self._section_headers
|
141
|
+
self.hdr.config = self._config_header
|
142
|
+
self.hdr.tls = self._tls_header
|
143
|
+
self.hdr.exceptions = self._exception_header
|
144
|
+
|
145
|
+
# We load the exception directory last as it relies on hdr.file to be created above.
|
146
|
+
self._exception_header = _load_exception_directory()
|
147
|
+
end
|
148
|
+
|
149
|
+
#
|
150
|
+
# Return everything that's going to be mapped in the process
|
151
|
+
# and accessable. This should include all of the sections
|
152
|
+
# and our "fake" section for the header data...
|
153
|
+
#
|
154
|
+
def all_sections
|
155
|
+
[ header_section ] + sections
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# Returns true if this binary is for a 64-bit architecture.
|
160
|
+
#
|
161
|
+
def ptr_64?
|
162
|
+
[
|
163
|
+
IMAGE_FILE_MACHINE_IA64,
|
164
|
+
IMAGE_FILE_MACHINE_ALPHA64,
|
165
|
+
IMAGE_FILE_MACHINE_AMD64
|
166
|
+
].include?(self._file_header.Machine)
|
167
|
+
end
|
168
|
+
|
169
|
+
#
|
170
|
+
# Returns true if this binary is for a 32-bit architecture.
|
171
|
+
# This check does not take into account 16-bit binaries at the moment.
|
172
|
+
#
|
173
|
+
def ptr_32?
|
174
|
+
ptr_64? == false
|
175
|
+
end
|
176
|
+
|
177
|
+
#
|
178
|
+
# Converts a virtual address to a string representation based on the
|
179
|
+
# underlying architecture.
|
180
|
+
#
|
181
|
+
def ptr_s(va)
|
182
|
+
(ptr_32?) ? ("0x%.8x" % va) : ("0x%.16x" % va)
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# Converts a file offset into a virtual address
|
187
|
+
#
|
188
|
+
def file_offset_to_va(offset)
|
189
|
+
image_base + file_offset_to_rva(offset)
|
190
|
+
end
|
191
|
+
|
192
|
+
#
|
193
|
+
# Read raw bytes from the specified offset in the underlying file
|
194
|
+
#
|
195
|
+
# NOTE: You should pass raw file offsets into this, not offsets from
|
196
|
+
# the beginning of the section. If you need to read from within a
|
197
|
+
# section, add section.file_offset prior to passing the offset in.
|
198
|
+
#
|
199
|
+
def read(offset, len)
|
200
|
+
_isource.read(offset, len)
|
201
|
+
end
|
202
|
+
|
203
|
+
def size
|
204
|
+
_isource.size
|
205
|
+
end
|
206
|
+
def length
|
207
|
+
_isource.size
|
208
|
+
end
|
209
|
+
|
210
|
+
end end end
|