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,25 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
|
3
|
+
module Rex
|
4
|
+
module ElfParsey
|
5
|
+
|
6
|
+
class ElfError < ::RuntimeError
|
7
|
+
end
|
8
|
+
|
9
|
+
class ParseError < ElfError
|
10
|
+
end
|
11
|
+
|
12
|
+
class ElfHeaderError < ParseError
|
13
|
+
end
|
14
|
+
|
15
|
+
class ProgramHeaderError < ParseError
|
16
|
+
end
|
17
|
+
|
18
|
+
class BoundsError < ElfError
|
19
|
+
end
|
20
|
+
|
21
|
+
class ElfParseyError < ElfError
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/rex/elfscan.rb
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
require 'metasm'
|
3
|
+
|
4
|
+
module Rex
|
5
|
+
module ElfScan
|
6
|
+
module Scanner
|
7
|
+
class Generic
|
8
|
+
|
9
|
+
attr_accessor :elf, :regex
|
10
|
+
|
11
|
+
def initialize(elf)
|
12
|
+
self.elf = elf
|
13
|
+
end
|
14
|
+
|
15
|
+
def config(param)
|
16
|
+
end
|
17
|
+
|
18
|
+
def scan(param)
|
19
|
+
config(param)
|
20
|
+
|
21
|
+
$stdout.puts "[#{param['file']}]"
|
22
|
+
elf.program_header.each do |program_header|
|
23
|
+
|
24
|
+
# Scan only loadable segment entries in the program header table
|
25
|
+
if program_header.p_type == Rex::ElfParsey::ElfBase::PT_LOAD
|
26
|
+
hits = scan_segment(program_header, param)
|
27
|
+
hits.each do |hit|
|
28
|
+
rva = hit[0]
|
29
|
+
message = hit[1].is_a?(Array) ? hit[1].join(" ") : hit[1]
|
30
|
+
$stdout.puts elf.ptr_s(rva) + " " + message
|
31
|
+
if(param['disasm'])
|
32
|
+
message.gsub!("; ", "\n")
|
33
|
+
if message.include?("retn")
|
34
|
+
message.gsub!("retn", "ret")
|
35
|
+
end
|
36
|
+
|
37
|
+
begin
|
38
|
+
d2 = Metasm::Shellcode.assemble(Metasm::Ia32.new, message).disassemble
|
39
|
+
rescue Metasm::ParseError
|
40
|
+
d2 = Metasm::Shellcode.disassemble(Metasm::Ia32.new, [message].pack('H*'))
|
41
|
+
end
|
42
|
+
|
43
|
+
addr = 0
|
44
|
+
while ((di = d2.disassemble_instruction(addr)))
|
45
|
+
disasm = "0x%08x\t" % (rva + addr)
|
46
|
+
disasm << di.instruction.to_s
|
47
|
+
$stdout.puts disasm
|
48
|
+
addr = di.next_addr
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def scan_segment(program_header, param={})
|
58
|
+
[]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class JmpRegScanner < Generic
|
63
|
+
|
64
|
+
def config(param)
|
65
|
+
regnums = param['args']
|
66
|
+
|
67
|
+
# build a list of the call bytes
|
68
|
+
calls = _build_byte_list(0xd0, regnums - [4]) # note call esp's don't work..
|
69
|
+
jmps = _build_byte_list(0xe0, regnums)
|
70
|
+
pushs1 = _build_byte_list(0x50, regnums)
|
71
|
+
pushs2 = _build_byte_list(0xf0, regnums)
|
72
|
+
|
73
|
+
regexstr = '('
|
74
|
+
if !calls.empty?
|
75
|
+
regexstr += "\xff[#{calls}]|"
|
76
|
+
end
|
77
|
+
|
78
|
+
regexstr += "\xff[#{jmps}]|([#{pushs1}]|\xff[#{pushs2}])(\xc3|\xc2..))"
|
79
|
+
|
80
|
+
self.regex = Regexp.new(regexstr, nil, 'n')
|
81
|
+
end
|
82
|
+
|
83
|
+
# build a list for regex of the possible bytes, based on a base
|
84
|
+
# byte and a list of register numbers..
|
85
|
+
def _build_byte_list(base, regnums)
|
86
|
+
regnums.collect { |regnum| Regexp.escape((base | regnum).chr) }.join('')
|
87
|
+
end
|
88
|
+
|
89
|
+
def _ret_size(offset)
|
90
|
+
case elf.read(offset, 1)
|
91
|
+
when "\xc3"
|
92
|
+
return 1
|
93
|
+
when "\xc2"
|
94
|
+
return 3
|
95
|
+
end
|
96
|
+
|
97
|
+
raise "Cannot read at offset: #{offset}"
|
98
|
+
end
|
99
|
+
|
100
|
+
def _parse_ret(data)
|
101
|
+
if data.length == 1
|
102
|
+
return "ret"
|
103
|
+
else
|
104
|
+
return "retn 0x%04x" % data[1, 2].unpack('v')[0]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def scan_segment(program_header, param={})
|
110
|
+
offset = program_header.p_offset
|
111
|
+
|
112
|
+
hits = []
|
113
|
+
|
114
|
+
while (offset = elf.index(regex, offset)) != nil
|
115
|
+
|
116
|
+
rva = elf.offset_to_rva(offset)
|
117
|
+
message = ''
|
118
|
+
|
119
|
+
parse_ret = false
|
120
|
+
|
121
|
+
byte1 = elf.read(offset, 1).unpack('C')[0]
|
122
|
+
|
123
|
+
if byte1 == 0xff
|
124
|
+
byte2 = elf.read(offset+1, 1).unpack('C')[0]
|
125
|
+
regname = Rex::Arch::X86.reg_name32(byte2 & 0x7)
|
126
|
+
|
127
|
+
case byte2 & 0xf8
|
128
|
+
when 0xd0
|
129
|
+
message = "call #{regname}"
|
130
|
+
offset += 2
|
131
|
+
when 0xe0
|
132
|
+
message = "jmp #{regname}"
|
133
|
+
offset += 2
|
134
|
+
when 0xf0
|
135
|
+
retsize = _ret_size(offset+2)
|
136
|
+
message = "push #{regname}; " + _parse_ret(elf.read(offset+2, retsize))
|
137
|
+
offset += 2 + retsize
|
138
|
+
else
|
139
|
+
raise "Unexpected value at #{offset}"
|
140
|
+
end
|
141
|
+
else
|
142
|
+
regname = Rex::Arch::X86.reg_name32(byte1 & 0x7)
|
143
|
+
retsize = _ret_size(offset+1)
|
144
|
+
message = "push #{regname}; " + _parse_ret(elf.read(offset+1, retsize))
|
145
|
+
offset += 1 + retsize
|
146
|
+
end
|
147
|
+
|
148
|
+
hits << [ rva, message ]
|
149
|
+
end
|
150
|
+
|
151
|
+
return hits
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class PopPopRetScanner < JmpRegScanner
|
156
|
+
|
157
|
+
def config(param)
|
158
|
+
pops = _build_byte_list(0x58, (0 .. 7).to_a - [4]) # we don't want pop esp's...
|
159
|
+
self.regex = Regexp.new("[#{pops}][#{pops}](\xc3|\xc2..)", nil, 'n')
|
160
|
+
end
|
161
|
+
|
162
|
+
def scan_segment(program_header, param={})
|
163
|
+
offset = program_header.p_offset
|
164
|
+
|
165
|
+
hits = []
|
166
|
+
|
167
|
+
while offset < program_header.p_offset + program_header.p_filesz &&
|
168
|
+
(offset = elf.index(regex, offset)) != nil
|
169
|
+
|
170
|
+
rva = elf.offset_to_rva(offset)
|
171
|
+
message = ''
|
172
|
+
|
173
|
+
pops = elf.read(offset, 2)
|
174
|
+
reg1 = Rex::Arch::X86.reg_name32(pops[0,1].unpack('C*')[0] & 0x7)
|
175
|
+
reg2 = Rex::Arch::X86.reg_name32(pops[1,1].unpack('C*')[0] & 0x7)
|
176
|
+
|
177
|
+
message = "pop #{reg1}; pop #{reg2}; "
|
178
|
+
|
179
|
+
retsize = _ret_size(offset+2)
|
180
|
+
message += _parse_ret(elf.read(offset+2, retsize))
|
181
|
+
|
182
|
+
offset += 2 + retsize
|
183
|
+
|
184
|
+
hits << [ rva, message ]
|
185
|
+
end
|
186
|
+
|
187
|
+
return hits
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class RegexScanner < JmpRegScanner
|
192
|
+
|
193
|
+
def config(param)
|
194
|
+
self.regex = Regexp.new(param['args'], nil, 'n')
|
195
|
+
end
|
196
|
+
|
197
|
+
def scan_segment(program_header, param={})
|
198
|
+
offset = program_header.p_offset
|
199
|
+
|
200
|
+
hits = []
|
201
|
+
|
202
|
+
while offset < program_header.p_offset + program_header.p_filesz &&
|
203
|
+
(offset = elf.index(regex, offset)) != nil
|
204
|
+
|
205
|
+
idx = offset
|
206
|
+
buf = ''
|
207
|
+
mat = nil
|
208
|
+
|
209
|
+
while (! (mat = buf.match(regex)))
|
210
|
+
buf << elf.read(idx, 1)
|
211
|
+
idx += 1
|
212
|
+
end
|
213
|
+
|
214
|
+
rva = elf.offset_to_rva(offset)
|
215
|
+
|
216
|
+
hits << [ rva, buf.unpack("H*") ]
|
217
|
+
offset += buf.length
|
218
|
+
end
|
219
|
+
|
220
|
+
return hits
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
|
3
|
+
module Rex
|
4
|
+
module ElfScan
|
5
|
+
module Search
|
6
|
+
|
7
|
+
class DumpRVA
|
8
|
+
attr_accessor :elf
|
9
|
+
|
10
|
+
def initialize(elf)
|
11
|
+
self.elf = elf
|
12
|
+
end
|
13
|
+
|
14
|
+
def config(param)
|
15
|
+
@address = param['args']
|
16
|
+
end
|
17
|
+
|
18
|
+
def scan(param)
|
19
|
+
config(param)
|
20
|
+
|
21
|
+
$stdout.puts "[#{param['file']}]"
|
22
|
+
|
23
|
+
# Adjust based on -A and -B flags
|
24
|
+
pre = param['before'] || 0
|
25
|
+
suf = param['after'] || 16
|
26
|
+
|
27
|
+
@address -= pre
|
28
|
+
@address = 0 if (@address < 0 || ! @address)
|
29
|
+
buf = elf.read_rva(@address, suf)
|
30
|
+
$stdout.puts elf.ptr_s(@address) + " " + buf.unpack("H*")[0]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class DumpOffset < DumpRVA
|
35
|
+
def config(param)
|
36
|
+
begin
|
37
|
+
@address = elf.offset_to_rva(param['args'])
|
38
|
+
rescue Rex::ElfParsey::BoundsError
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
|
3
|
+
require 'rex/image_source/image_source'
|
4
|
+
require 'rex/struct2'
|
5
|
+
|
6
|
+
module Rex
|
7
|
+
module ImageSource
|
8
|
+
class Disk < ImageSource
|
9
|
+
|
10
|
+
attr_accessor :file, :file_offset, :size
|
11
|
+
|
12
|
+
WINDOW_SIZE = 4096
|
13
|
+
WINDOW_OVERLAP = 64
|
14
|
+
|
15
|
+
def initialize(_file, _offset = 0, _len = nil)
|
16
|
+
_len = _file.stat.size if !_len
|
17
|
+
|
18
|
+
self.file = _file
|
19
|
+
self.file_offset = _offset
|
20
|
+
self.size = _len
|
21
|
+
end
|
22
|
+
|
23
|
+
def read(offset, len)
|
24
|
+
if offset < 0 || offset+len > size
|
25
|
+
raise RangeError, "Offset #{offset} outside of image source", caller
|
26
|
+
end
|
27
|
+
|
28
|
+
file.seek(file_offset + offset)
|
29
|
+
file.read(len)
|
30
|
+
end
|
31
|
+
|
32
|
+
def index(search, offset = 0)
|
33
|
+
# do a sliding window search across the disk
|
34
|
+
while offset < size
|
35
|
+
|
36
|
+
# get a full window size if we can, we
|
37
|
+
# don't want to read past our boundaries
|
38
|
+
wsize = size - offset
|
39
|
+
wsize = WINDOW_SIZE if wsize > WINDOW_SIZE
|
40
|
+
|
41
|
+
window = self.read(offset, wsize)
|
42
|
+
res = window.index(search)
|
43
|
+
return res + offset if res
|
44
|
+
offset += WINDOW_SIZE - WINDOW_OVERLAP
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def subsource(offset, len)
|
49
|
+
self.class.new(file, file_offset+offset, len)
|
50
|
+
end
|
51
|
+
|
52
|
+
def close
|
53
|
+
file.close
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
|
3
|
+
module Rex
|
4
|
+
module ImageSource
|
5
|
+
class ImageSource
|
6
|
+
|
7
|
+
#
|
8
|
+
# Um, just some abstract class stuff I guess, this is the interface
|
9
|
+
# that any image sources should subscribe to...
|
10
|
+
#
|
11
|
+
|
12
|
+
def subsource(offset, len)
|
13
|
+
raise "do something"
|
14
|
+
end
|
15
|
+
|
16
|
+
def size
|
17
|
+
raise "do something"
|
18
|
+
end
|
19
|
+
|
20
|
+
def file_offset
|
21
|
+
raise "do something"
|
22
|
+
end
|
23
|
+
|
24
|
+
def close
|
25
|
+
raise "do something"
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_asciiz(offset)
|
29
|
+
# FIXME, make me better
|
30
|
+
string = ''
|
31
|
+
loop do
|
32
|
+
begin
|
33
|
+
char = read(offset, 1)
|
34
|
+
rescue RangeError
|
35
|
+
break
|
36
|
+
end
|
37
|
+
break if char.nil? || char == "\x00"
|
38
|
+
offset += 1
|
39
|
+
string << char
|
40
|
+
end
|
41
|
+
return string
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- coding: binary -*-
|
2
|
+
|
3
|
+
require 'rex/image_source/image_source'
|
4
|
+
require 'rex/struct2'
|
5
|
+
|
6
|
+
module Rex
|
7
|
+
module ImageSource
|
8
|
+
class Memory < ImageSource
|
9
|
+
|
10
|
+
attr_accessor :rawdata, :size, :file_offset
|
11
|
+
|
12
|
+
def initialize(_rawdata, _file_offset = 0)
|
13
|
+
self.rawdata = _rawdata
|
14
|
+
self.size = _rawdata.length
|
15
|
+
self.file_offset = _file_offset
|
16
|
+
end
|
17
|
+
|
18
|
+
def read(offset, len)
|
19
|
+
rawdata[offset, len]
|
20
|
+
end
|
21
|
+
|
22
|
+
def subsource(offset, len)
|
23
|
+
self.class.new(rawdata[offset, len], offset + file_offset)
|
24
|
+
end
|
25
|
+
|
26
|
+
def close
|
27
|
+
end
|
28
|
+
|
29
|
+
def index(*args)
|
30
|
+
rawdata.index(*args)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|