lib/pwnlib/util/cyclic.rb
# encoding: ASCII-8BIT
# frozen_string_literal: true
module Pwnlib
module Util
# Generate string with easy-to-find pattern.
#
# @example Call by specifying full module path.
# require 'pwnlib/util/cyclic'
# Pwnlib::Util::Cyclic.cyclic_find(Pwnlib::Util::Cyclic.cyclic(200)[123, 4]) #=> 123
# @example require 'pwn' and have all methods.
# require 'pwn'
# cyclic_find(cyclic(200)[123, 4]) #=> 123
module Cyclic
# TODO(Darkpi): Should we put this constant in some 'String' module?
ASCII_LOWERCASE = ('a'..'z').to_a.join
private_constant :ASCII_LOWERCASE
module_function
# Generator for a sequence of unique substrings of length +n+.
# This is implemented using a De Bruijn Sequence over the given +alphabet+.
# Returns an Enumerator if no block given.
#
# @overload de_bruijn(alphabet: ASCII_LOWERCASE, n: 4)
# @param [String, Array] alphabet
# Alphabet to be used.
# @param [Integer] n
# Length of substring that should be unique.
#
# @return [void]
#
# @yieldparam c
# Item of the result sequence in order.
#
# @overload de_bruijn(alphabet: ASCII_LOWERCASE, n: 4)
# @param [String, Array] alphabet
# Alphabet to be used.
# @param [Integer] n
# Length of substring that should be unique.
#
# @return [Enumerator]
# The result sequence.
def de_bruijn(alphabet: ASCII_LOWERCASE, n: 4)
return to_enum(__method__, alphabet: alphabet, n: n) { alphabet.size**n } unless block_given?
k = alphabet.size
a = [0] * (k * n)
db = lambda do |t, p|
if t > n
(1..p).each { |j| yield alphabet[a[j]] } if (n % p).zero?
else
a[t] = a[t - p]
db.call(t + 1, p)
(a[t - p] + 1...k).each do |j|
a[t] = j
db.call(t + 1, t)
end
end
end
db[1, 1]
end
# Simple wrapper over {.de_bruijn}, returning at most +length+ items.
#
# @param [Integer, nil] length
# Desired length of the sequence, or +nil+ for the entire sequence.
# @param [String, Array] alphabet
# Alphabet to be used.
# @param [Integer] n
# Length of substring that should be unique.
#
# @return [String, Array]
# The result sequence of at most +length+ items, with same type as +alphabet+.
#
# @example
# cyclic(alphabet: 'ABC', n: 3) #=> 'AAABAACABBABCACBACCBBBCBCCC'
# cyclic(20) #=> 'aaaabaaacaaadaaaeaaa'
def cyclic(length = nil, alphabet: ASCII_LOWERCASE, n: 4)
enum = de_bruijn(alphabet: alphabet, n: n)
r = length.nil? ? enum.to_a : enum.take(length)
alphabet.is_a?(String) ? r.join : r
end
# Find the position of a substring in a De Bruijn sequence.
#
# @param [String, Array] subseq
# The substring to be found in the sequence.
# @param [String, Array] alphabet
# Alphabet to be used.
# @param [Integer] n
# Length of substring that should be unique.
# Default to +subseq.size+.
#
# @return [Integer, nil]
# The index +subseq+ first appear in the sequence, or +nil+ if not found.
#
# @todo Speed! See comment in Python pwntools.
#
# @example
# cyclic_find(cyclic(300)[217, 4]) #=> 217
def cyclic_find(subseq, alphabet: ASCII_LOWERCASE, n: nil)
n ||= subseq.size
subseq = subseq.chars if subseq.is_a?(String)
return nil unless subseq.all? { |c| alphabet.include?(c) }
pos = 0
saved = []
de_bruijn(alphabet: alphabet, n: n).each do |c|
saved << c
if saved.size > subseq.size
saved.shift
pos += 1
end
return pos if saved == subseq
end
nil
end
end
end
end