rex-bin_tools 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|