From ac0909141c1787e32e9c9efc4a51b52fed0d32aa Mon Sep 17 00:00:00 2001 From: adfoster-r7 Date: Fri, 27 Mar 2026 11:44:34 +0000 Subject: [PATCH] Remove support for legacy Ruby versions --- .github/workflows/verify.yml | 29 ++++++++++++++++ lib/rex/elfscan/scanner.rb | 39 +++------------------- lib/rex/machscan/scanner.rb | 39 +++------------------- lib/rex/pescan/analyze.rb | 18 ++-------- lib/rex/pescan/scanner.rb | 39 +++------------------- spec/lib/rex/elfscan/scanner_spec.rb | 48 +++++++++++++++++++++++++++ spec/lib/rex/machscan/scanner_spec.rb | 48 +++++++++++++++++++++++++++ spec/lib/rex/pescan/analyze_spec.rb | 33 ++++++++++++++++++ spec/lib/rex/pescan/scanner_spec.rb | 48 +++++++++++++++++++++++++++ 9 files changed, 221 insertions(+), 120 deletions(-) create mode 100644 .github/workflows/verify.yml create mode 100644 spec/lib/rex/elfscan/scanner_spec.rb create mode 100644 spec/lib/rex/machscan/scanner_spec.rb create mode 100644 spec/lib/rex/pescan/analyze_spec.rb create mode 100644 spec/lib/rex/pescan/scanner_spec.rb diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 0000000..7a610e6 --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,29 @@ +name: Verify + +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions +permissions: + actions: none + checks: none + contents: none + deployments: none + id-token: none + issues: none + discussions: none + packages: none + pages: none + pull-requests: none + repository-projects: none + security-events: none + statuses: none + +on: + push: + branches: + - '*' + pull_request: + branches: + - '*' + +jobs: + build: + uses: rapid7/metasploit-framework/.github/workflows/shared_gem_verify.yml@master diff --git a/lib/rex/elfscan/scanner.rb b/lib/rex/elfscan/scanner.rb index 2c9bbaf..db5092d 100644 --- a/lib/rex/elfscan/scanner.rb +++ b/lib/rex/elfscan/scanner.rb @@ -75,18 +75,9 @@ def config(param) regexstr += "\xff[#{calls}]|" end - # Adapting to Regexp.new's New Signature in Ruby 3.3+ regexstr += "\xff[#{jmps}]|([#{pushs1}]|\xff[#{pushs2}])(\xc3|\xc2..))" - # Choose initialization method based on Ruby version - major, minor, _patch = RUBY_VERSION.split('.').map(&:to_i) - self.regex = if (major > 3) || (major == 3 && minor >= 3) - # For Ruby 3.3+: explicitly mark as binary pattern and use NOENCODING - binary_pattern = regexstr.b - Regexp.new(binary_pattern, Regexp::NOENCODING) - else - # For Ruby <= 3.2: use legacy three-argument syntax - Regexp.new(regexstr, nil, 'n') - end + + self.regex = Regexp.new(regexstr, Regexp::NOENCODING) end # build a list for regex of the possible bytes, based on a base @@ -165,18 +156,7 @@ class PopPopRetScanner < JmpRegScanner def config(param) pops = _build_byte_list(0x58, (0 .. 7).to_a - [4]) # we don't want pop esp's... - # Adapting to Regexp.new's New Signature in Ruby 3.3+ - pattern = "[#{pops}][#{pops}](\xc3|\xc2..)" - # Choose initialization method based on Ruby version - major, minor, _patch = RUBY_VERSION.split('.').map(&:to_i) - self.regex = if (major > 3) || (major == 3 && minor >= 3) - # For Ruby 3.3+: explicitly mark as binary pattern and use NOENCODING - binary_pattern = pattern.b - Regexp.new(binary_pattern, Regexp::NOENCODING) - else - # For Ruby <= 3.2: use legacy three-argument syntax - Regexp.new(pattern, nil, 'n') - end + self.regex = Regexp.new("[#{pops}][#{pops}](\xc3|\xc2..)", Regexp::NOENCODING) end def scan_segment(program_header, param={}) @@ -211,18 +191,7 @@ def scan_segment(program_header, param={}) class RegexScanner < JmpRegScanner def config(param) - # Adapting to Regexp.new's New Signature in Ruby 3.3+ - pattern = param['args'] - # Choose initialization method based on Ruby version - major, minor, _patch = RUBY_VERSION.split('.').map(&:to_i) - self.regex = if (major > 3) || (major == 3 && minor >= 3) - # For Ruby 3.3+: explicitly mark as binary pattern and use NOENCODING - binary_pattern = pattern.b - Regexp.new(binary_pattern, Regexp::NOENCODING) - else - # For Ruby <= 3.2: use legacy three-argument syntax - Regexp.new(pattern, nil, 'n') - end + self.regex = Regexp.new(param['args'], Regexp::NOENCODING) end def scan_segment(program_header, param={}) diff --git a/lib/rex/machscan/scanner.rb b/lib/rex/machscan/scanner.rb index 011ba39..288e4dd 100644 --- a/lib/rex/machscan/scanner.rb +++ b/lib/rex/machscan/scanner.rb @@ -64,18 +64,9 @@ def config(param) regexstr += "\xff[#{calls}]|" end - # Adapting to Regexp.new's New Signature in Ruby 3.3+ regexstr += "\xff[#{jmps}]|([#{pushs1}]|\xff[#{pushs2}])(\xc3|\xc2..))" - # Choose initialization method based on Ruby version - major, minor, _patch = RUBY_VERSION.split('.').map(&:to_i) - self.regex = if (major > 3) || (major == 3 && minor >= 3) - # For Ruby 3.3+: explicitly mark as binary pattern and use NOENCODING - binary_pattern = regexstr.b - Regexp.new(binary_pattern, Regexp::NOENCODING) - else - # For Ruby <= 3.2: use legacy three-argument syntax - Regexp.new(regexstr, nil, 'n') - end + + self.regex = Regexp.new(regexstr, Regexp::NOENCODING) end # build a list for regex of the possible bytes, based on a base @@ -154,18 +145,7 @@ class PopPopRetScanner < JmpRegScanner def config(param) pops = _build_byte_list(0x58, (0 .. 7).to_a - [4]) # we don't want pop esp's... - # Adapting to Regexp.new's New Signature in Ruby 3.3+ - pattern = "[#{pops}][#{pops}](\xc3|\xc2..)" - # Choose initialization method based on Ruby version - major, minor, _patch = RUBY_VERSION.split('.').map(&:to_i) - self.regex = if (major > 3) || (major == 3 && minor >= 3) - # For Ruby 3.3+: explicitly mark as binary pattern and use NOENCODING - binary_pattern = pattern.b - Regexp.new(binary_pattern, Regexp::NOENCODING) - else - # For Ruby <= 3.2: use legacy three-argument syntax - Regexp.new(pattern, nil, 'n') - end + self.regex = Regexp.new("[#{pops}][#{pops}](\xc3|\xc2..)", Regexp::NOENCODING) end def scan_segment(segment, param={}) @@ -201,18 +181,7 @@ def scan_segment(segment, param={}) class RegexScanner < JmpRegScanner def config(param) - # Adapting to Regexp.new's New Signature in Ruby 3.3+ - pattern = param['args'] - # Choose initialization method based on Ruby version - major, minor, _patch = RUBY_VERSION.split('.').map(&:to_i) - self.regex = if (major > 3) || (major == 3 && minor >= 3) - # For Ruby 3.3+: explicitly mark as binary pattern and use NOENCODING - binary_pattern = pattern.b - Regexp.new(binary_pattern, Regexp::NOENCODING) - else - # For Ruby <= 3.2: use legacy three-argument syntax - Regexp.new(pattern, nil, 'n') - end + self.regex = Regexp.new(param['args'], Regexp::NOENCODING) end def scan_segment(segment, param={}) diff --git a/lib/rex/pescan/analyze.rb b/lib/rex/pescan/analyze.rb index bf42287..4c098dd 100644 --- a/lib/rex/pescan/analyze.rb +++ b/lib/rex/pescan/analyze.rb @@ -59,21 +59,9 @@ def scan(param) @sigs.each_pair do |name, data| begin - # Adapting to Regexp.new's New Signature in Ruby 3.3+ - pattern = '^' + data[0] - # Choose initialization method based on Ruby version - major, minor, _patch = RUBY_VERSION.split('.').map(&:to_i) - regex = if (major > 3) || (major == 3 && minor >= 3) - # For Ruby 3.3+: explicitly mark as binary pattern and use NOENCODING - binary_pattern = pattern.b - Regexp.new(binary_pattern, Regexp::NOENCODING) - else - # For Ruby <= 3.2: use legacy three-argument syntax - Regexp.new(pattern, nil, 'n') - end - if (buf.match(regex)) - $stdout.puts param['file'] + ": " + name - end + if (buf.match(Regexp.new('^' + data[0], Regexp::NOENCODING))) + $stdout.puts param['file'] + ": " + name + end rescue RegexpError $stderr.puts "Invalid signature: #{name} #{data[0]}" end diff --git a/lib/rex/pescan/scanner.rb b/lib/rex/pescan/scanner.rb index 13f62c6..ac4ebd1 100644 --- a/lib/rex/pescan/scanner.rb +++ b/lib/rex/pescan/scanner.rb @@ -80,18 +80,9 @@ def config(param) regexstr += "\xff[#{calls}]|" end - # Adapting to Regexp.new's New Signature in Ruby 3.3+ regexstr += "\xff[#{jmps}]|([#{pushs1}]|\xff[#{pushs2}])(\xc3|\xc2..))" - # Choose initialization method based on Ruby version - major, minor, _patch = RUBY_VERSION.split('.').map(&:to_i) - self.regex = if (major > 3) || (major == 3 && minor >= 3) - # For Ruby 3.3+: explicitly mark as binary pattern and use NOENCODING - binary_pattern = regexstr.b - Regexp.new(binary_pattern, Regexp::NOENCODING) - else - # For Ruby <= 3.2: use legacy three-argument syntax - Regexp.new(regexstr, nil, 'n') - end + + self.regex = Regexp.new(regexstr, Regexp::NOENCODING) end # build a list for regex of the possible bytes, based on a base @@ -170,18 +161,7 @@ class PopPopRetScanner < JmpRegScanner def config(param) pops = _build_byte_list(0x58, (0 .. 7).to_a - [4]) # we don't want pop esp's... - # Adapting to Regexp.new's New Signature in Ruby 3.3+ - pattern = "[#{pops}][#{pops}](\xc3|\xc2..)" - # Choose initialization method based on Ruby version - major, minor, _patch = RUBY_VERSION.split('.').map(&:to_i) - self.regex = if (major > 3) || (major == 3 && minor >= 3) - # For Ruby 3.3+: explicitly mark as binary pattern and use NOENCODING - binary_pattern = pattern.b - Regexp.new(binary_pattern, Regexp::NOENCODING) - else - # For Ruby <= 3.2: use legacy three-argument syntax - Regexp.new(pattern, nil, 'n') - end + self.regex = Regexp.new("[#{pops}][#{pops}](\xc3|\xc2..)", Regexp::NOENCODING) end def scan_section(section, param={}) @@ -215,18 +195,7 @@ def scan_section(section, param={}) class RegexScanner < Generic def config(param) - # Adapting to Regexp.new's New Signature in Ruby 3.3+ - pattern = param['args'] - # Choose initialization method based on Ruby version - major, minor, _patch = RUBY_VERSION.split('.').map(&:to_i) - self.regex = if (major > 3) || (major == 3 && minor >= 3) - # For Ruby 3.3+: explicitly mark as binary pattern and use NOENCODING - binary_pattern = pattern.b - Regexp.new(binary_pattern, Regexp::NOENCODING) - else - # For Ruby <= 3.2: use legacy three-argument syntax - Regexp.new(pattern, nil, 'n') - end + self.regex = Regexp.new(param['args'], Regexp::NOENCODING) end def scan_section(section, param={}) diff --git a/spec/lib/rex/elfscan/scanner_spec.rb b/spec/lib/rex/elfscan/scanner_spec.rb new file mode 100644 index 0000000..366c366 --- /dev/null +++ b/spec/lib/rex/elfscan/scanner_spec.rb @@ -0,0 +1,48 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/elfscan/scanner' +require 'rex/elfparsey' + +RSpec.describe Rex::ElfScan::Scanner do + let(:elf) { instance_double(Rex::ElfParsey::Elf) } + + describe Rex::ElfScan::Scanner::JmpRegScanner do + subject { described_class.new(elf) } + + describe '#config' do + it 'creates a regex with NOENCODING for jmp reg patterns' do + param = { 'args' => [0, 1, 2, 3, 5, 6, 7] } + expect { subject.config(param) }.not_to raise_error + expect(subject.regex).to be_a(Regexp) + expect(subject.regex.encoding).to eq(Encoding::ASCII_8BIT) + end + end + end + + describe Rex::ElfScan::Scanner::PopPopRetScanner do + subject { described_class.new(elf) } + + describe '#config' do + it 'creates a regex with NOENCODING for pop pop ret patterns' do + param = {} + expect { subject.config(param) }.not_to raise_error + expect(subject.regex).to be_a(Regexp) + expect(subject.regex.encoding).to eq(Encoding::ASCII_8BIT) + end + end + end + + describe Rex::ElfScan::Scanner::RegexScanner do + subject { described_class.new(elf) } + + describe '#config' do + it 'creates a regex with NOENCODING from user-supplied pattern' do + param = { 'args' => '\xcc' } + expect { subject.config(param) }.not_to raise_error + expect(subject.regex).to be_a(Regexp) + expect(subject.regex.encoding).to eq(Encoding::ASCII_8BIT) + end + end + end +end diff --git a/spec/lib/rex/machscan/scanner_spec.rb b/spec/lib/rex/machscan/scanner_spec.rb new file mode 100644 index 0000000..7b33d66 --- /dev/null +++ b/spec/lib/rex/machscan/scanner_spec.rb @@ -0,0 +1,48 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/machparsey' +require 'rex/machscan/scanner' + +RSpec.describe Rex::MachScan::Scanner do + let(:mach) { instance_double(Rex::MachParsey::Mach) } + + describe Rex::MachScan::Scanner::JmpRegScanner do + subject { described_class.new(mach) } + + describe '#config' do + it 'creates a regex with NOENCODING for jmp reg patterns' do + param = { 'args' => [0, 1, 2, 3, 5, 6, 7] } + expect { subject.config(param) }.not_to raise_error + expect(subject.regex).to be_a(Regexp) + expect(subject.regex.encoding).to eq(Encoding::ASCII_8BIT) + end + end + end + + describe Rex::MachScan::Scanner::PopPopRetScanner do + subject { described_class.new(mach) } + + describe '#config' do + it 'creates a regex with NOENCODING for pop pop ret patterns' do + param = {} + expect { subject.config(param) }.not_to raise_error + expect(subject.regex).to be_a(Regexp) + expect(subject.regex.encoding).to eq(Encoding::ASCII_8BIT) + end + end + end + + describe Rex::MachScan::Scanner::RegexScanner do + subject { described_class.new(mach) } + + describe '#config' do + it 'creates a regex with NOENCODING from user-supplied pattern' do + param = { 'args' => '\xcc' } + expect { subject.config(param) }.not_to raise_error + expect(subject.regex).to be_a(Regexp) + expect(subject.regex.encoding).to eq(Encoding::ASCII_8BIT) + end + end + end +end diff --git a/spec/lib/rex/pescan/analyze_spec.rb b/spec/lib/rex/pescan/analyze_spec.rb new file mode 100644 index 0000000..41dd96c --- /dev/null +++ b/spec/lib/rex/pescan/analyze_spec.rb @@ -0,0 +1,33 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/pescan/analyze' +require 'rex/peparsey' + +RSpec.describe Rex::PeScan::Analyze::Fingerprint do + let(:opt) { double('opt', AddressOfEntryPoint: 0) } + let(:hdr) { double('hdr', opt: opt) } + let(:pe) { instance_double(Rex::PeParsey::Pe, hdr: hdr, read_rva: "MZ\x90\x00".b) } + + subject { described_class.new(pe) } + + let(:param) do + { + 'database' => File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'data', 'identify.txt'), + 'file' => 'test.exe' + } + end + + describe '#config' do + it 'loads signatures from the database without error' do + expect { subject.config(param) }.not_to raise_error + end + end + + describe '#scan' do + it 'matches signatures using Regexp with NOENCODING after config' do + subject.config(param) + expect { subject.scan(param) }.not_to raise_error + end + end +end diff --git a/spec/lib/rex/pescan/scanner_spec.rb b/spec/lib/rex/pescan/scanner_spec.rb new file mode 100644 index 0000000..bf9e53e --- /dev/null +++ b/spec/lib/rex/pescan/scanner_spec.rb @@ -0,0 +1,48 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/pescan/scanner' +require 'rex/peparsey' + +RSpec.describe Rex::PeScan::Scanner do + let(:pe) { instance_double(Rex::PeParsey::Pe) } + + describe Rex::PeScan::Scanner::JmpRegScanner do + subject { described_class.new(pe) } + + describe '#config' do + it 'creates a regex with NOENCODING for jmp reg patterns' do + param = { 'args' => [0, 1, 2, 3, 5, 6, 7] } + expect { subject.config(param) }.not_to raise_error + expect(subject.regex).to be_a(Regexp) + expect(subject.regex.encoding).to eq(Encoding::ASCII_8BIT) + end + end + end + + describe Rex::PeScan::Scanner::PopPopRetScanner do + subject { described_class.new(pe) } + + describe '#config' do + it 'creates a regex with NOENCODING for pop pop ret patterns' do + param = {} + expect { subject.config(param) }.not_to raise_error + expect(subject.regex).to be_a(Regexp) + expect(subject.regex.encoding).to eq(Encoding::ASCII_8BIT) + end + end + end + + describe Rex::PeScan::Scanner::RegexScanner do + subject { described_class.new(pe) } + + describe '#config' do + it 'creates a regex with NOENCODING from user-supplied pattern' do + param = { 'args' => '\xcc' } + expect { subject.config(param) }.not_to raise_error + expect(subject.regex).to be_a(Regexp) + expect(subject.regex.encoding).to eq(Encoding::ASCII_8BIT) + end + end + end +end