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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +1 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gitignore +9 -0
  5. data/.rspec +2 -0
  6. data/.travis.yml +5 -0
  7. data/CODE_OF_CONDUCT.md +52 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE +27 -0
  10. data/README.md +22 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/msfbinscan +284 -0
  14. data/bin/msfelfscan +120 -0
  15. data/bin/msfmachscan +100 -0
  16. data/bin/msfpescan +184 -0
  17. data/bin/setup +8 -0
  18. data/data/identify.txt +3043 -0
  19. data/lib/rex/assembly/nasm.rb +104 -0
  20. data/lib/rex/bin_tools.rb +13 -0
  21. data/lib/rex/bin_tools/version.rb +5 -0
  22. data/lib/rex/elfparsey.rb +9 -0
  23. data/lib/rex/elfparsey/elf.rb +121 -0
  24. data/lib/rex/elfparsey/elfbase.rb +265 -0
  25. data/lib/rex/elfparsey/exceptions.rb +25 -0
  26. data/lib/rex/elfscan.rb +10 -0
  27. data/lib/rex/elfscan/scanner.rb +226 -0
  28. data/lib/rex/elfscan/search.rb +44 -0
  29. data/lib/rex/image_source.rb +10 -0
  30. data/lib/rex/image_source/disk.rb +58 -0
  31. data/lib/rex/image_source/image_source.rb +48 -0
  32. data/lib/rex/image_source/memory.rb +35 -0
  33. data/lib/rex/machparsey.rb +9 -0
  34. data/lib/rex/machparsey/exceptions.rb +31 -0
  35. data/lib/rex/machparsey/mach.rb +209 -0
  36. data/lib/rex/machparsey/machbase.rb +408 -0
  37. data/lib/rex/machscan.rb +9 -0
  38. data/lib/rex/machscan/scanner.rb +217 -0
  39. data/lib/rex/peparsey.rb +10 -0
  40. data/lib/rex/peparsey/exceptions.rb +30 -0
  41. data/lib/rex/peparsey/pe.rb +210 -0
  42. data/lib/rex/peparsey/pe_memdump.rb +61 -0
  43. data/lib/rex/peparsey/pebase.rb +1662 -0
  44. data/lib/rex/peparsey/section.rb +128 -0
  45. data/lib/rex/pescan.rb +11 -0
  46. data/lib/rex/pescan/analyze.rb +366 -0
  47. data/lib/rex/pescan/scanner.rb +230 -0
  48. data/lib/rex/pescan/search.rb +68 -0
  49. data/rex-bin_tools.gemspec +32 -0
  50. metadata +284 -0
  51. metadata.gz.sig +0 -0
@@ -0,0 +1,9 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module MachScan
5
+
6
+ end
7
+ end
8
+
9
+ require 'rex/machscan/scanner'
@@ -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
+
@@ -0,0 +1,10 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module PeParsey
5
+
6
+ end
7
+ end
8
+
9
+ require 'rex/peparsey/pe'
10
+ require 'rex/peparsey/pe_memdump'
@@ -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