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
@@ -0,0 +1,128 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
|
3
|
+
require 'rex/peparsey/exceptions'
|
4
|
+
require 'rex/peparsey/pebase'
|
5
|
+
require 'rex/struct2'
|
6
|
+
|
7
|
+
module Rex
|
8
|
+
module PeParsey
|
9
|
+
class Section
|
10
|
+
attr_accessor :_section_header, :_isource
|
11
|
+
attr_accessor :base_rva
|
12
|
+
|
13
|
+
#
|
14
|
+
# Initialize a section.
|
15
|
+
#
|
16
|
+
# isource - The ImageSource class backing the image
|
17
|
+
# base_vma - The address of this section base
|
18
|
+
# section_header - The section header (struct2) although this is not
|
19
|
+
# required, which is why there is a base_vma. This can be nil.
|
20
|
+
#
|
21
|
+
def initialize(isource, base_rva, section_header = nil)
|
22
|
+
self._isource = isource
|
23
|
+
self.base_rva = base_rva
|
24
|
+
self._section_header = section_header
|
25
|
+
end
|
26
|
+
|
27
|
+
def file_offset
|
28
|
+
_isource.file_offset
|
29
|
+
end
|
30
|
+
|
31
|
+
def size
|
32
|
+
_isource.size
|
33
|
+
end
|
34
|
+
|
35
|
+
def name
|
36
|
+
# a section header is not required
|
37
|
+
return nil if !_section_header
|
38
|
+
|
39
|
+
# FIXME make this better...
|
40
|
+
_section_header.v['Name'].gsub(/\x00+$/n, '')
|
41
|
+
end
|
42
|
+
|
43
|
+
def flags
|
44
|
+
# a section header is not required
|
45
|
+
return nil if !_section_header
|
46
|
+
_section_header.v['Characteristics']
|
47
|
+
end
|
48
|
+
|
49
|
+
def vma
|
50
|
+
# a section header is not required
|
51
|
+
return nil if !_section_header
|
52
|
+
_section_header.v['VirtualAddress']
|
53
|
+
end
|
54
|
+
|
55
|
+
def raw_size
|
56
|
+
# a section header is not required
|
57
|
+
return nil if !_section_header
|
58
|
+
_section_header.v['SizeOfRawData']
|
59
|
+
end
|
60
|
+
|
61
|
+
def _check_offset(offset, len = 1)
|
62
|
+
if offset < 0 || offset+len > size
|
63
|
+
raise BoundsError, "Offset #{offset} outside of section", caller
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def read(offset, len)
|
68
|
+
_check_offset(offset, len)
|
69
|
+
return _isource.read(offset, len)
|
70
|
+
end
|
71
|
+
|
72
|
+
def read_rva(rva, len)
|
73
|
+
return read(rva_to_offset(rva), len)
|
74
|
+
end
|
75
|
+
|
76
|
+
def read_asciiz(offset)
|
77
|
+
_check_offset(offset)
|
78
|
+
return _isource.read_asciiz(offset)
|
79
|
+
end
|
80
|
+
|
81
|
+
def read_asciiz_rva(rva)
|
82
|
+
return read_asciiz(rva_to_offset(rva))
|
83
|
+
end
|
84
|
+
|
85
|
+
def index(*args)
|
86
|
+
_isource.index(*args)
|
87
|
+
end
|
88
|
+
|
89
|
+
def offset_to_rva(offset)
|
90
|
+
if !contains_offset?(offset)
|
91
|
+
raise BoundsError, "Offset #{offset} outside of section", caller
|
92
|
+
end
|
93
|
+
|
94
|
+
return offset + base_rva
|
95
|
+
end
|
96
|
+
|
97
|
+
def file_offset_to_rva(foffset)
|
98
|
+
return offset_to_rva(foffset - file_offset)
|
99
|
+
end
|
100
|
+
|
101
|
+
def rva_to_offset(rva)
|
102
|
+
offset = rva - base_rva
|
103
|
+
if !contains_offset?(offset)
|
104
|
+
raise BoundsError, "RVA #{rva} outside of section", caller
|
105
|
+
end
|
106
|
+
|
107
|
+
return offset
|
108
|
+
end
|
109
|
+
|
110
|
+
def rva_to_file_offset(rva)
|
111
|
+
return rva_to_offset(rva) + file_offset
|
112
|
+
end
|
113
|
+
|
114
|
+
def contains_offset?(offset)
|
115
|
+
offset >= 0 && offset < size
|
116
|
+
end
|
117
|
+
|
118
|
+
def contains_file_offset?(foffset)
|
119
|
+
contains_offset?(foffset - file_offset)
|
120
|
+
end
|
121
|
+
|
122
|
+
def contains_rva?(rva)
|
123
|
+
contains_offset?(rva - base_rva)
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end end
|
data/lib/rex/pescan.rb
ADDED
@@ -0,0 +1,366 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
module Rex
|
3
|
+
module PeScan
|
4
|
+
module Analyze
|
5
|
+
|
6
|
+
require "rex/text/table"
|
7
|
+
|
8
|
+
class Fingerprint
|
9
|
+
attr_accessor :pe
|
10
|
+
|
11
|
+
def initialize(pe)
|
12
|
+
self.pe = pe
|
13
|
+
end
|
14
|
+
|
15
|
+
def config(param)
|
16
|
+
@sigs = {}
|
17
|
+
|
18
|
+
name = nil
|
19
|
+
regx = ''
|
20
|
+
epon = 0
|
21
|
+
sidx = 0
|
22
|
+
|
23
|
+
fd = File.open(param['database'], 'rb')
|
24
|
+
fd.each_line do |line|
|
25
|
+
case line
|
26
|
+
when /^\s*#/
|
27
|
+
next
|
28
|
+
when /\[\s*(.*)\s*\]/
|
29
|
+
if (name)
|
30
|
+
@sigs[ name ] = [regx, epon]
|
31
|
+
end
|
32
|
+
name = $1 + " [#{ sidx+=1 }]"
|
33
|
+
epon = 0
|
34
|
+
next
|
35
|
+
when /signature\s*=\s*(.*)/
|
36
|
+
pat = $1.strip
|
37
|
+
regx = ''
|
38
|
+
pat.split(/\s+/).each do |c|
|
39
|
+
next if c.length != 2
|
40
|
+
regx << (c.index('?') ? '.' : "\\x#{c}")
|
41
|
+
end
|
42
|
+
when /ep_only\s*=\s*(.*)/
|
43
|
+
epon = ($1 =~ /^T/i) ? 1 : 0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if (name and ! @sigs[name])
|
48
|
+
@sigs[ name ] = [regx, epon]
|
49
|
+
end
|
50
|
+
|
51
|
+
fd.close
|
52
|
+
end
|
53
|
+
|
54
|
+
def scan(param)
|
55
|
+
config(param)
|
56
|
+
|
57
|
+
epa = pe.hdr.opt.AddressOfEntryPoint
|
58
|
+
buf = pe.read_rva(epa, 256) || ""
|
59
|
+
|
60
|
+
@sigs.each_pair do |name, data|
|
61
|
+
begin
|
62
|
+
if (buf.match(Regexp.new('^' + data[0], nil, 'n')))
|
63
|
+
$stdout.puts param['file'] + ": " + name
|
64
|
+
end
|
65
|
+
rescue RegexpError
|
66
|
+
$stderr.puts "Invalid signature: #{name} #{data[0]}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Information
|
73
|
+
attr_accessor :pe
|
74
|
+
|
75
|
+
def initialize(pe)
|
76
|
+
self.pe = pe
|
77
|
+
end
|
78
|
+
|
79
|
+
def add_fields(tbl, obj, fields)
|
80
|
+
fields.each do |name|
|
81
|
+
begin
|
82
|
+
tbl << [name, "0x%.8x" % obj.send(name)]
|
83
|
+
rescue ::NoMethodError => e
|
84
|
+
$stderr.puts "Invalid field #{name}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def scan(param)
|
90
|
+
|
91
|
+
$stdout.puts "\n\n"
|
92
|
+
|
93
|
+
tbl = table("Image Headers", ['Name', 'Value'])
|
94
|
+
add_fields(tbl, pe.hdr.file, %W{
|
95
|
+
Characteristics
|
96
|
+
SizeOfOptionalHeader
|
97
|
+
PointerToSymbolTable
|
98
|
+
TimeDateStamp
|
99
|
+
NumberOfSections
|
100
|
+
Machine
|
101
|
+
})
|
102
|
+
$stdout.puts tbl.to_s
|
103
|
+
$stdout.puts "\n\n"
|
104
|
+
|
105
|
+
tbl = table("Optional Image Headers", ['Name', 'Value'])
|
106
|
+
add_fields(tbl, pe.hdr.opt, %W{
|
107
|
+
ImageBase
|
108
|
+
Magic
|
109
|
+
MajorLinkerVersion
|
110
|
+
MinorLinkerVersion
|
111
|
+
SizeOfCode
|
112
|
+
SizeOfInitializeData
|
113
|
+
SizeOfUninitializeData
|
114
|
+
AddressOfEntryPoint
|
115
|
+
BaseOfCode
|
116
|
+
BaseOfData
|
117
|
+
SectionAlignment
|
118
|
+
FileAlignment
|
119
|
+
MajorOperatingSystemVersion
|
120
|
+
MinorOperatingSystemVersion
|
121
|
+
MajorImageVersion
|
122
|
+
MinorImageVersion
|
123
|
+
MajorSubsystemVersion
|
124
|
+
MinorSubsystemVersion
|
125
|
+
Win32VersionValue
|
126
|
+
SizeOfImage
|
127
|
+
SizeOfHeaders
|
128
|
+
CheckSum
|
129
|
+
Subsystem
|
130
|
+
DllCharacteristics
|
131
|
+
SizeOfStackReserve
|
132
|
+
SizeOfStackCommit
|
133
|
+
SizeOfHeapReserve
|
134
|
+
SizeOfHeapCommit
|
135
|
+
LoaderFlags
|
136
|
+
NumberOfRvaAndSizes
|
137
|
+
})
|
138
|
+
|
139
|
+
$stdout.puts tbl.to_s
|
140
|
+
$stdout.puts "\n\n"
|
141
|
+
|
142
|
+
# Get DllCharacteristics (in Integer)
|
143
|
+
dllcharacteristics = pe.hdr.opt.struct[23].value
|
144
|
+
|
145
|
+
if (dllcharacteristics > 0)
|
146
|
+
tbl = table("DllCharacteristics", ['Flag', 'Value'])
|
147
|
+
|
148
|
+
# http://msdn.microsoft.com/en-us/library/ms680339(v=vs.85).aspx
|
149
|
+
traits = {
|
150
|
+
:ASLR => 'False', #IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
|
151
|
+
:Integrity => 'False', #IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY
|
152
|
+
:NX => 'False', #IMAGE_DLLCHARACTERISTICS_NX_COMPAT
|
153
|
+
:Isolation => 'False', #IMAGE_DLLCHARACTERISTICS_NO_ISOLATION
|
154
|
+
:SEH => 'False', #IMAGE_DLLCHARACTERISTICS_NO_SEH
|
155
|
+
:Bind => 'False', #IMAGE_DLLCHARACTERISTICS_NO_BIND
|
156
|
+
:WDM => 'False', #IMAGE_DLLCHARACTERISTICS_WDM_DRIVER
|
157
|
+
:Terminal => 'False' #IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE
|
158
|
+
}
|
159
|
+
|
160
|
+
# Convert integer to an bit array
|
161
|
+
c_bits = ("%32d" %dllcharacteristics.to_s(2)).split('').map { |e| e.to_i }.reverse
|
162
|
+
|
163
|
+
# Check characteristics
|
164
|
+
traits[:ASLR] = 'True' if c_bits[6] == 1 #0x0040
|
165
|
+
traits[:Integrity] = 'True' if c_bits[7] == 1 #0x0080
|
166
|
+
traits[:NX] = 'True' if c_bits[8] == 1 #0x0100
|
167
|
+
traits[:Isolation] = 'True' if c_bits[9] == 1 #0x0200
|
168
|
+
traits[:SEH] = 'True' if c_bits[10] == 1 #0x0400
|
169
|
+
traits[:Bind] = 'True' if c_bits[11] == 1 #0x0800
|
170
|
+
traits[:WDM] = 'True' if c_bits[13] == 1 #2000
|
171
|
+
traits[:Terminal] = 'True' if c_bits[15] == 1 #0x8000
|
172
|
+
|
173
|
+
# Putting results to table
|
174
|
+
traits.each do |trait_name, trait_value|
|
175
|
+
tbl << [trait_name, trait_value]
|
176
|
+
end
|
177
|
+
|
178
|
+
$stdout.puts tbl.to_s
|
179
|
+
$stdout.puts "\n\n"
|
180
|
+
end
|
181
|
+
|
182
|
+
if (pe.exports)
|
183
|
+
tbl = table("Exported Functions", ['Ordinal', 'Name', 'Address'])
|
184
|
+
pe.exports.entries.each do |ent|
|
185
|
+
tbl << [ent.ordinal, ent.name, "0x%.8x" % pe.rva_to_vma(ent.rva)]
|
186
|
+
end
|
187
|
+
$stdout.puts tbl.to_s
|
188
|
+
$stdout.puts "\n\n"
|
189
|
+
end
|
190
|
+
|
191
|
+
# Rex::PeParsey::Pe doesn't seem to give us any offset information for each function,
|
192
|
+
# which makes it difficult to calculate the actual addresses for them. So instead we
|
193
|
+
# are using Metasm::COFF::ImportDirectory to do this task. The ability to see
|
194
|
+
# addresses is mainly for ROP.
|
195
|
+
if (pe.imports)
|
196
|
+
tbl = table("Imported Functions", ['Library', 'Address', 'Ordinal', 'Name'])
|
197
|
+
exefmt = Metasm::AutoExe.orshellcode{ Metasm.const_get('x86_64').new }
|
198
|
+
exe = exefmt.decode_file(pe._isource.file.path)
|
199
|
+
ibase = pe.image_base
|
200
|
+
exe_imports = exe.imports
|
201
|
+
exe_imports.each do |lib|
|
202
|
+
lib_name = lib.libname
|
203
|
+
ini_offset = lib.iat_p
|
204
|
+
func_table = lib.imports
|
205
|
+
offset = 0
|
206
|
+
func_table.each do |func|
|
207
|
+
func_addr = "0x%08x" %(ibase + ini_offset + offset)
|
208
|
+
tbl << [lib_name, func_addr, func.hint, func.name]
|
209
|
+
offset += 4
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
$stdout.puts tbl.to_s
|
214
|
+
$stdout.puts "\n\n"
|
215
|
+
end
|
216
|
+
|
217
|
+
if(pe.config)
|
218
|
+
tbl = table("Configuration Header", ['Name', 'Value'])
|
219
|
+
add_fields(tbl, pe.config, %W{
|
220
|
+
Size
|
221
|
+
TimeDateStamp
|
222
|
+
MajorVersion
|
223
|
+
MinorVersion
|
224
|
+
GlobalFlagsClear
|
225
|
+
GlobalFlagsSet
|
226
|
+
CriticalSectionDefaultTimeout
|
227
|
+
DeCommitFreeBlockThreshold
|
228
|
+
DeCommitTotalFreeThreshold
|
229
|
+
LockPrefixTable
|
230
|
+
MaximumAllocationSize
|
231
|
+
VirtualMemoryThreshold
|
232
|
+
ProcessAffinityMask
|
233
|
+
ProcessHeapFlags
|
234
|
+
CSDVersion
|
235
|
+
Reserved1
|
236
|
+
EditList
|
237
|
+
SecurityCookie
|
238
|
+
SEHandlerTable
|
239
|
+
SEHandlerCount
|
240
|
+
})
|
241
|
+
$stdout.puts tbl.to_s
|
242
|
+
$stdout.puts "\n\n"
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
if(pe.resources)
|
247
|
+
tbl = table("Resources", ['ID', 'Language', 'Code Page', 'Size', 'Name'])
|
248
|
+
pe.resources.keys.sort.each do |rkey|
|
249
|
+
res = pe.resources[rkey]
|
250
|
+
tbl << [rkey, res.lang, res.code, res.size, res.file]
|
251
|
+
end
|
252
|
+
$stdout.puts tbl.to_s
|
253
|
+
$stdout.puts "\n\n"
|
254
|
+
end
|
255
|
+
|
256
|
+
tbl = table("Section Header", ["Name", "VirtualAddress", "SizeOfRawData", "Characteristics"])
|
257
|
+
pe.sections.each do |sec|
|
258
|
+
tbl << [ sec.name, *[sec.vma, sec.raw_size, sec.flags].map{|x| "0x%.8x" % x} ]
|
259
|
+
end
|
260
|
+
$stdout.puts tbl.to_s
|
261
|
+
$stdout.puts "\n\n"
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
def table(name, cols)
|
266
|
+
Rex::Text::Table.new(
|
267
|
+
'Header' => name,
|
268
|
+
'Columns' => cols
|
269
|
+
)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
|
274
|
+
class Ripper
|
275
|
+
|
276
|
+
require "fileutils"
|
277
|
+
|
278
|
+
attr_accessor :pe
|
279
|
+
|
280
|
+
def initialize(pe)
|
281
|
+
self.pe = pe
|
282
|
+
end
|
283
|
+
|
284
|
+
def scan(param)
|
285
|
+
dest = param['dir']
|
286
|
+
|
287
|
+
if (param['file'])
|
288
|
+
dest = File.join(dest, File.basename(param['file']))
|
289
|
+
end
|
290
|
+
|
291
|
+
::FileUtils.mkdir_p(dest)
|
292
|
+
|
293
|
+
pe.resources.keys.sort.each do |rkey|
|
294
|
+
res = pe.resources[rkey]
|
295
|
+
path = File.join(dest, rkey.split('/')[1] + '_' + res.file)
|
296
|
+
|
297
|
+
fd = File.new(path, 'wb')
|
298
|
+
fd.write(res.data)
|
299
|
+
fd.close
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
class ContextMapDumper
|
305
|
+
|
306
|
+
attr_accessor :pe
|
307
|
+
|
308
|
+
def initialize(pe)
|
309
|
+
self.pe = pe
|
310
|
+
end
|
311
|
+
|
312
|
+
def scan(param)
|
313
|
+
dest = param['dir']
|
314
|
+
path = ''
|
315
|
+
|
316
|
+
::FileUtils.mkdir_p(dest)
|
317
|
+
|
318
|
+
if(not (param['dir'] and param['file']))
|
319
|
+
$stderr.puts "No directory or file specified"
|
320
|
+
return
|
321
|
+
end
|
322
|
+
|
323
|
+
if (param['file'])
|
324
|
+
path = File.join(dest, File.basename(param['file']) + ".map")
|
325
|
+
end
|
326
|
+
|
327
|
+
fd = File.new(path, "wb")
|
328
|
+
pe.all_sections.each do |section|
|
329
|
+
|
330
|
+
# Skip over known bad sections
|
331
|
+
next if section.name == ".data"
|
332
|
+
next if section.name == ".reloc"
|
333
|
+
|
334
|
+
offset = 0
|
335
|
+
while offset < section.size
|
336
|
+
byte = section.read(offset, 1)[0]
|
337
|
+
if byte != 0
|
338
|
+
chunkbase = pe.rva_to_vma(section.base_rva) + offset
|
339
|
+
data = ''
|
340
|
+
while byte != 0
|
341
|
+
data << byte
|
342
|
+
offset += 1
|
343
|
+
byte = 0
|
344
|
+
byte = section.read(offset, 1)[0] if offset < section.size
|
345
|
+
end
|
346
|
+
buff = nil
|
347
|
+
buff = [ 0x01, chunkbase, data.length, data].pack("CNNA*") if data.length > 0
|
348
|
+
|
349
|
+
fd.write(buff) if buff
|
350
|
+
end
|
351
|
+
offset += 1
|
352
|
+
end
|
353
|
+
|
354
|
+
end
|
355
|
+
|
356
|
+
|
357
|
+
fd.close
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
# EOC
|
362
|
+
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|