pwntools 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -11
  3. data/Rakefile +5 -1
  4. data/lib/pwn.rb +9 -7
  5. data/lib/pwnlib/abi.rb +60 -0
  6. data/lib/pwnlib/asm.rb +146 -0
  7. data/lib/pwnlib/constants/constant.rb +16 -2
  8. data/lib/pwnlib/constants/constants.rb +35 -19
  9. data/lib/pwnlib/constants/linux/amd64.rb +30 -1
  10. data/lib/pwnlib/context.rb +25 -17
  11. data/lib/pwnlib/dynelf.rb +117 -54
  12. data/lib/pwnlib/elf/elf.rb +267 -0
  13. data/lib/pwnlib/ext/helper.rb +4 -4
  14. data/lib/pwnlib/logger.rb +87 -0
  15. data/lib/pwnlib/memleak.rb +58 -29
  16. data/lib/pwnlib/pwn.rb +19 -8
  17. data/lib/pwnlib/reg_sort.rb +102 -108
  18. data/lib/pwnlib/shellcraft/generators/amd64/common/common.rb +14 -0
  19. data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +17 -0
  20. data/lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb +31 -0
  21. data/lib/pwnlib/shellcraft/generators/amd64/common/mov.rb +127 -0
  22. data/lib/pwnlib/shellcraft/generators/amd64/common/nop.rb +16 -0
  23. data/lib/pwnlib/shellcraft/generators/amd64/common/popad.rb +27 -0
  24. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb +64 -0
  25. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +19 -0
  26. data/lib/pwnlib/shellcraft/generators/amd64/common/ret.rb +32 -0
  27. data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +19 -0
  28. data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +21 -0
  29. data/lib/pwnlib/shellcraft/generators/amd64/linux/linux.rb +14 -0
  30. data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +19 -0
  31. data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +19 -0
  32. data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +21 -0
  33. data/lib/pwnlib/shellcraft/generators/helper.rb +106 -0
  34. data/lib/pwnlib/shellcraft/generators/i386/common/common.rb +14 -0
  35. data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +17 -0
  36. data/lib/pwnlib/shellcraft/generators/i386/common/mov.rb +90 -0
  37. data/lib/pwnlib/shellcraft/generators/i386/common/nop.rb +16 -0
  38. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr.rb +39 -0
  39. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +19 -0
  40. data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +19 -0
  41. data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +19 -0
  42. data/lib/pwnlib/shellcraft/generators/i386/linux/linux.rb +14 -0
  43. data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +19 -0
  44. data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +19 -0
  45. data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +19 -0
  46. data/lib/pwnlib/shellcraft/generators/x86/common/common.rb +26 -0
  47. data/lib/pwnlib/shellcraft/generators/x86/common/infloop.rb +22 -0
  48. data/lib/pwnlib/shellcraft/generators/x86/common/mov.rb +15 -0
  49. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr.rb +15 -0
  50. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr_array.rb +85 -0
  51. data/lib/pwnlib/shellcraft/generators/x86/common/setregs.rb +82 -0
  52. data/lib/pwnlib/shellcraft/generators/x86/linux/execve.rb +69 -0
  53. data/lib/pwnlib/shellcraft/generators/x86/linux/linux.rb +14 -0
  54. data/lib/pwnlib/shellcraft/generators/x86/linux/ls.rb +66 -0
  55. data/lib/pwnlib/shellcraft/generators/x86/linux/sh.rb +52 -0
  56. data/lib/pwnlib/shellcraft/generators/x86/linux/syscall.rb +52 -0
  57. data/lib/pwnlib/shellcraft/registers.rb +145 -0
  58. data/lib/pwnlib/shellcraft/shellcraft.rb +67 -0
  59. data/lib/pwnlib/timer.rb +60 -0
  60. data/lib/pwnlib/tubes/buffer.rb +96 -0
  61. data/lib/pwnlib/tubes/sock.rb +95 -0
  62. data/lib/pwnlib/tubes/tube.rb +270 -0
  63. data/lib/pwnlib/util/cyclic.rb +95 -94
  64. data/lib/pwnlib/util/fiddling.rb +256 -220
  65. data/lib/pwnlib/util/getdents.rb +83 -0
  66. data/lib/pwnlib/util/hexdump.rb +109 -108
  67. data/lib/pwnlib/util/lists.rb +55 -0
  68. data/lib/pwnlib/util/packing.rb +226 -228
  69. data/lib/pwnlib/util/ruby.rb +18 -0
  70. data/lib/pwnlib/version.rb +2 -1
  71. data/test/abi_test.rb +21 -0
  72. data/test/asm_test.rb +104 -0
  73. data/test/constants/constant_test.rb +1 -0
  74. data/test/constants/constants_test.rb +4 -2
  75. data/test/context_test.rb +1 -0
  76. data/test/data/echo.rb +20 -0
  77. data/test/data/elfs/Makefile +22 -0
  78. data/test/data/elfs/amd64.frelro.elf +0 -0
  79. data/test/data/elfs/amd64.frelro.pie.elf +0 -0
  80. data/test/data/elfs/amd64.nrelro.elf +0 -0
  81. data/test/data/elfs/amd64.prelro.elf +0 -0
  82. data/test/data/elfs/i386.frelro.pie.elf +0 -0
  83. data/test/data/elfs/i386.prelro.elf +0 -0
  84. data/test/data/elfs/source.cpp +19 -0
  85. data/test/data/flag +1 -0
  86. data/test/data/lib32/ld.so.2 +0 -0
  87. data/test/data/lib32/libc.so.6 +0 -0
  88. data/test/data/lib64/ld.so.2 +0 -0
  89. data/test/data/lib64/libc.so.6 +0 -0
  90. data/test/dynelf_test.rb +59 -24
  91. data/test/elf/elf_test.rb +120 -0
  92. data/test/ext_test.rb +3 -2
  93. data/test/files/use_pwnlib.rb +1 -1
  94. data/test/logger_test.rb +61 -0
  95. data/test/memleak_test.rb +4 -33
  96. data/test/reg_sort_test.rb +3 -1
  97. data/test/shellcraft/infloop_test.rb +26 -0
  98. data/test/shellcraft/linux/ls_test.rb +108 -0
  99. data/test/shellcraft/linux/sh_test.rb +119 -0
  100. data/test/shellcraft/linux/syscalls/execve_test.rb +136 -0
  101. data/test/shellcraft/linux/syscalls/syscall_test.rb +83 -0
  102. data/test/shellcraft/memcpy_test.rb +35 -0
  103. data/test/shellcraft/mov_test.rb +98 -0
  104. data/test/shellcraft/nop_test.rb +26 -0
  105. data/test/shellcraft/popad_test.rb +29 -0
  106. data/test/shellcraft/pushstr_array_test.rb +91 -0
  107. data/test/shellcraft/pushstr_test.rb +108 -0
  108. data/test/shellcraft/registers_test.rb +32 -0
  109. data/test/shellcraft/ret_test.rb +30 -0
  110. data/test/shellcraft/setregs_test.rb +62 -0
  111. data/test/shellcraft/shellcraft_test.rb +28 -0
  112. data/test/test_helper.rb +12 -1
  113. data/test/timer_test.rb +23 -0
  114. data/test/tubes/buffer_test.rb +45 -0
  115. data/test/tubes/sock_test.rb +68 -0
  116. data/test/tubes/tube_test.rb +241 -0
  117. data/test/util/cyclic_test.rb +2 -1
  118. data/test/util/fiddling_test.rb +2 -1
  119. data/test/util/getdents_test.rb +32 -0
  120. data/test/util/hexdump_test.rb +7 -9
  121. data/test/util/lists_test.rb +21 -0
  122. data/test/util/packing_test.rb +4 -3
  123. metadata +215 -25
@@ -0,0 +1,270 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/context'
4
+ require 'pwnlib/logger'
5
+ require 'pwnlib/timer'
6
+ require 'pwnlib/tubes/buffer'
7
+ require 'pwnlib/util/hexdump'
8
+
9
+ module Pwnlib
10
+ module Tubes
11
+ # Things common to all tubes (sockets, tty, ...)
12
+ # @!macro [new] timeout_definition
13
+ # @param [Float] timeout
14
+ # Any positive floating number, indicates timeout in seconds.
15
+ # Using +context.timeout+ if +timeout+ equals to +nil+.
16
+ class Tube
17
+ BUFSIZE = 4096
18
+
19
+ # Instantiate a {Pwnlib::Tubes::Tube} object.
20
+ #
21
+ # @!macro timeout_definition
22
+ def initialize(timeout: nil)
23
+ @timer = Timer.new(timeout)
24
+ @buffer = Buffer.new
25
+ end
26
+
27
+ # Receives up to +num_bytes+ bytes of data from the tube, and returns as soon as any quantity
28
+ # of data is available.
29
+ #
30
+ # @param [Integer] num_bytes
31
+ # The maximum number of bytes to receive.
32
+ # @!macro timeout_definition
33
+ #
34
+ # @return [String]
35
+ # A string contains bytes received from the tube, or +''+ if a timeout occurred while
36
+ # waiting.
37
+ def recv(num_bytes = nil, timeout: nil)
38
+ return '' if @buffer.empty? && !fillbuffer(timeout: timeout)
39
+ @buffer.get(num_bytes)
40
+ end
41
+ alias read recv
42
+
43
+ # Puts the specified data back at the beginning of the receive buffer.
44
+ #
45
+ # @param [String] data
46
+ # A string to put back.
47
+ def unrecv(data)
48
+ @buffer.unget(data)
49
+ end
50
+
51
+ # Receives one byte at a time from the tube, until the predicate evaluates to +true+.
52
+ #
53
+ # @!macro timeout_definition
54
+ #
55
+ # @return [String]
56
+ # A string contains bytes received from the tube, or +''+ if a timeout occurred while
57
+ # waiting.
58
+ #
59
+ # @yield
60
+ # A predicate to evaluate whether the data satisfy the condition.
61
+ #
62
+ # @yieldparam [String] data
63
+ # A string data to be validated by the predicate.
64
+ #
65
+ # @yieldreturn [Boolean]
66
+ # Whether the data satisfy the condition.
67
+ #
68
+ # @raise [ArgumentError]
69
+ # If the block is not given.
70
+ def recvpred(timeout: nil)
71
+ raise ArgumentError, 'Need a block for recvpred' unless block_given?
72
+ @timer.countdown(timeout) do
73
+ data = ''
74
+ begin
75
+ until yield(data)
76
+ return '' unless @timer.active?
77
+
78
+ begin
79
+ # TODO(Darkpi): Some form of binary search to speed up?
80
+ c = recv(1)
81
+ rescue
82
+ return ''
83
+ end
84
+
85
+ return '' if c.empty?
86
+ data << c
87
+ end
88
+ data.slice!(0..-1)
89
+ ensure
90
+ unrecv(data)
91
+ end
92
+ end
93
+ end
94
+
95
+ # Receives exactly +num_bytes+ bytes.
96
+ # If the request is not satisfied before +timeout+ seconds pass, all data is buffered and an
97
+ # empty string +''+ is returned.
98
+ #
99
+ # @param [Integer] num_bytes
100
+ # The number of bytes to receive.
101
+ # @!macro timeout_definition
102
+ #
103
+ # @return [String]
104
+ # A string contains bytes received from the tube, or +''+ if a timeout occurred while
105
+ # waiting.
106
+ def recvn(num_bytes, timeout: nil)
107
+ @timer.countdown(timeout) do
108
+ fillbuffer while @timer.active? && @buffer.size < num_bytes
109
+ @buffer.size >= num_bytes ? @buffer.get(num_bytes) : ''
110
+ end
111
+ end
112
+
113
+ # Receive data until one of +delims+ is encountered. If the request is not satisfied before
114
+ # +timeout+ seconds pass, all data is buffered and an empty string is returned.
115
+ #
116
+ # @param [Array<String>] delims
117
+ # String of delimiters characters, or list of delimiter strings.
118
+ # @param [Boalean] drop
119
+ # Whether drop the ending.
120
+ # @!macro timeout_definition
121
+ #
122
+ # @return [String]
123
+ # A string contains bytes, which ends string in +delims+, received from the tube.
124
+ #
125
+ # @diff We return the string that ends the earliest, rather then starts the earliest,
126
+ # since the latter can't be done greedly. Still, it would be bad to call this
127
+ # for case with ambiguity.
128
+ def recvuntil(delims, drop: false, timeout: nil)
129
+ delims = Array(delims)
130
+ max_len = delims.map(&:size).max
131
+ @timer.countdown(timeout) do
132
+ data = Buffer.new
133
+ matching = ''
134
+ begin
135
+ while @timer.active?
136
+ begin
137
+ s = recv(1)
138
+ rescue # TODO(Darkpi): QQ
139
+ return ''
140
+ end
141
+
142
+ return '' if s.empty?
143
+ matching << s
144
+
145
+ sidx = matching.size
146
+ match_len = 0
147
+ delims.each do |d|
148
+ idx = matching.index(d)
149
+ next unless idx
150
+ if idx + d.size <= sidx + match_len
151
+ sidx = idx
152
+ match_len = d.size
153
+ end
154
+ end
155
+
156
+ if sidx < matching.size
157
+ r = data.get + matching.slice!(0, sidx + match_len)
158
+ r.slice!(-match_len..-1) if drop
159
+ return r
160
+ end
161
+
162
+ data << matching.slice!(0...-max_len) if matching.size > max_len
163
+ end
164
+ ''
165
+ ensure
166
+ unrecv(matching)
167
+ unrecv(data)
168
+ end
169
+ end
170
+ end
171
+
172
+ # Receive a single line from the tube.
173
+ # A "line" is any sequence of bytes terminated by the byte sequence set in +context.newline+,
174
+ # which defaults to +"\n"+.
175
+ #
176
+ # @param [Boolean] drop
177
+ # Whether drop the line ending.
178
+ # @!macro timeout_definition
179
+ #
180
+ # @return [String]
181
+ # All bytes received over the tube until the first newline is received.
182
+ # Optionally retains the ending.
183
+ def recvline(drop: false, timeout: nil)
184
+ recvuntil(context.newline, drop: drop, timeout: timeout)
185
+ end
186
+ alias gets recvline
187
+
188
+ # Wrapper around +recvpred+, which will return when a regex matches the string in the buffer.
189
+ #
190
+ # @param [Regexp] regex
191
+ # A regex to match.
192
+ # @!macro timeout_definition
193
+ #
194
+ # @return [String]
195
+ # A string contains bytes received from the tube, or +''+ if a timeout occurred while
196
+ # waiting.
197
+ def recvregex(regex, timeout: nil)
198
+ recvpred(timeout: timeout) { |data| data =~ regex }
199
+ end
200
+
201
+ # Sends data
202
+ #
203
+ # @param [String] data
204
+ # The +data+ string to send.
205
+ def send(data)
206
+ data = data.to_s
207
+ log.debug(format('Sent %#x bytes:', data.size))
208
+ log.indented(hexdump(data), level: DEBUG)
209
+ send_raw(data)
210
+ end
211
+ alias write send
212
+
213
+ # Sends data with +context.newline+.
214
+ #
215
+ # @param [String] data
216
+ # The +data+ string to send.
217
+ def sendline(data)
218
+ # Logged by +write+, not +send_raw+
219
+ write(data.to_s + context.newline)
220
+ end
221
+ alias puts sendline
222
+
223
+ # Does simultaneous reading and writing to the tube. In principle this just connects the tube
224
+ # to standard in and standard out.
225
+ def interact
226
+ log.info('Switching to interactive mode')
227
+ $stdout.write(@buffer.get)
228
+ until io.closed?
229
+ rs, = IO.select([$stdin, io])
230
+ if rs.include?($stdin)
231
+ s = $stdin.readpartial(BUFSIZE)
232
+ write(s)
233
+ end
234
+ if rs.include?(io)
235
+ s = recv
236
+ $stdout.write(s)
237
+ end
238
+ end
239
+ end
240
+
241
+ private
242
+
243
+ def fillbuffer(timeout: nil)
244
+ data = @timer.countdown(timeout) do
245
+ self.timeout_raw = @timer.timeout
246
+ recv_raw(BUFSIZE)
247
+ end
248
+ if data
249
+ @buffer << data
250
+ log.debug(format('Received %#x bytes:', data.size))
251
+ log.indented(hexdump(data), level: DEBUG)
252
+ end
253
+ data
254
+ end
255
+
256
+ def send_raw(_data); raise NotImplementedError, 'Not implemented'
257
+ end
258
+
259
+ def recv_raw(_size); raise NotImplementedError, 'Not implemented'
260
+ end
261
+
262
+ def timeout_raw=(_timeout); raise NotImplementedError, 'Not implemented'
263
+ end
264
+
265
+ include ::Pwnlib::Context
266
+ include ::Pwnlib::Logger
267
+ include ::Pwnlib::Util::HexDump
268
+ end
269
+ end
270
+ end
@@ -3,7 +3,7 @@
3
3
  module Pwnlib
4
4
  module Util
5
5
  # Generate string with easy-to-find pattern.
6
- # See {ClassMethods} for method details.
6
+ #
7
7
  # @example Call by specifying full module path.
8
8
  # require 'pwnlib/util/cyclic'
9
9
  # Pwnlib::Util::Cyclic.cyclic_find(Pwnlib::Util::Cyclic.cyclic(200)[123, 4]) #=> 123
@@ -11,110 +11,111 @@ module Pwnlib
11
11
  # require 'pwn'
12
12
  # cyclic_find(cyclic(200)[123, 4]) #=> 123
13
13
  module Cyclic
14
- # @note Do not create and call instance method here. Instead, call module method on {Cyclic}.
15
- module ClassMethods
16
- # TODO(Darkpi): Should we put this constant in some 'String' module?
17
- ASCII_LOWERCASE = ('a'..'z').to_a.join
18
- private_constant :ASCII_LOWERCASE
14
+ # TODO(Darkpi): Should we put this constant in some 'String' module?
15
+ ASCII_LOWERCASE = ('a'..'z').to_a.join
16
+ private_constant :ASCII_LOWERCASE
19
17
 
20
- # Generator for a sequence of unique substrings of length +n+.
21
- # This is implemented using a De Bruijn Sequence over the given +alphabet+.
22
- # Returns an Enumerator if no block given.
23
- #
24
- # @overload de_bruijn(alphabet: ASCII_LOWERCASE, n: 4)
25
- # @param [String, Array] alphabet
26
- # Alphabet to be used.
27
- # @param [Integer] n
28
- # Length of substring that should be unique.
29
- # @return [void]
30
- # @yieldparam c
31
- # Item of the result sequence in order.
32
- # @overload de_bruijn(alphabet: ASCII_LOWERCASE, n: 4)
33
- # @param [String, Array] alphabet
34
- # Alphabet to be used.
35
- # @param [Integer] n
36
- # Length of substring that should be unique.
37
- # @return [Enumerator]
38
- # The result sequence.
39
- def de_bruijn(alphabet: ASCII_LOWERCASE, n: 4)
40
- return to_enum(__method__, alphabet: alphabet, n: n) { alphabet.size**n } unless block_given?
41
- k = alphabet.size
42
- a = [0] * (k * n)
18
+ module_function
43
19
 
44
- db = lambda do |t, p|
45
- if t > n
46
- (1..p).each { |j| yield alphabet[a[j]] } if (n % p).zero?
47
- else
48
- a[t] = a[t - p]
49
- db.call(t + 1, p)
50
- (a[t - p] + 1...k).each do |j|
51
- a[t] = j
52
- db.call(t + 1, t)
53
- end
20
+ # Generator for a sequence of unique substrings of length +n+.
21
+ # This is implemented using a De Bruijn Sequence over the given +alphabet+.
22
+ # Returns an Enumerator if no block given.
23
+ #
24
+ # @overload de_bruijn(alphabet: ASCII_LOWERCASE, n: 4)
25
+ # @param [String, Array] alphabet
26
+ # Alphabet to be used.
27
+ # @param [Integer] n
28
+ # Length of substring that should be unique.
29
+ #
30
+ # @return [void]
31
+ #
32
+ # @yieldparam c
33
+ # Item of the result sequence in order.
34
+ #
35
+ # @overload de_bruijn(alphabet: ASCII_LOWERCASE, n: 4)
36
+ # @param [String, Array] alphabet
37
+ # Alphabet to be used.
38
+ # @param [Integer] n
39
+ # Length of substring that should be unique.
40
+ #
41
+ # @return [Enumerator]
42
+ # The result sequence.
43
+ def de_bruijn(alphabet: ASCII_LOWERCASE, n: 4)
44
+ return to_enum(__method__, alphabet: alphabet, n: n) { alphabet.size**n } unless block_given?
45
+ k = alphabet.size
46
+ a = [0] * (k * n)
47
+
48
+ db = lambda do |t, p|
49
+ if t > n
50
+ (1..p).each { |j| yield alphabet[a[j]] } if (n % p).zero?
51
+ else
52
+ a[t] = a[t - p]
53
+ db.call(t + 1, p)
54
+ (a[t - p] + 1...k).each do |j|
55
+ a[t] = j
56
+ db.call(t + 1, t)
54
57
  end
55
58
  end
56
-
57
- db[1, 1]
58
59
  end
59
60
 
60
- # Simple wrapper over {#de_bruijn}, returning at most +length+ items.
61
- #
62
- # @param [Integer, nil] length
63
- # Desired length of the sequence,
64
- # or +nil+ for the entire sequence.
65
- # @param [String, Array] alphabet
66
- # Alphabet to be used.
67
- # @param [Integer] n
68
- # Length of substring that should be unique.
69
- # @return [String, Array]
70
- # The result sequence of at most +length+ items,
71
- # with same type as +alphabet+.
72
- #
73
- # @example
74
- # cyclic(alphabet: 'ABC', n: 3) #=> 'AAABAACABBABCACBACCBBBCBCCC'
75
- # cyclic(20) #=> 'aaaabaaacaaadaaaeaaa'
76
- def cyclic(length = nil, alphabet: ASCII_LOWERCASE, n: 4)
77
- enum = de_bruijn(alphabet: alphabet, n: n)
78
- r = length.nil? ? enum.to_a : enum.take(length)
79
- alphabet.is_a?(String) ? r.join : r
80
- end
61
+ db[1, 1]
62
+ end
81
63
 
82
- # Find the position of a substring in a De Bruijn sequence
83
- #
84
- # @todo Speed! See comment in Python pwntools.
85
- # @param [String, Array] subseq
86
- # The substring to be found in the sequence.
87
- # @param [String, Array] alphabet
88
- # Alphabet to be used.
89
- # @param [Integer] n
90
- # Length of substring that should be unique.
91
- # Default to +subseq.size+.
92
- # @return [Integer, nil]
93
- # The index +subseq+ first appear in the sequence,
94
- # or +nil+ if not found.
95
- #
96
- # @example
97
- # cyclic_find(cyclic(300)[217, 4]) #=> 217
98
- def cyclic_find(subseq, alphabet: ASCII_LOWERCASE, n: nil)
99
- n ||= subseq.size
100
- subseq = subseq.chars if subseq.is_a?(String)
101
- return nil unless subseq.all? { |c| alphabet.include?(c) }
64
+ # Simple wrapper over {.de_bruijn}, returning at most +length+ items.
65
+ #
66
+ # @param [Integer, nil] length
67
+ # Desired length of the sequence, or +nil+ for the entire sequence.
68
+ # @param [String, Array] alphabet
69
+ # Alphabet to be used.
70
+ # @param [Integer] n
71
+ # Length of substring that should be unique.
72
+ #
73
+ # @return [String, Array]
74
+ # The result sequence of at most +length+ items, with same type as +alphabet+.
75
+ #
76
+ # @example
77
+ # cyclic(alphabet: 'ABC', n: 3) #=> 'AAABAACABBABCACBACCBBBCBCCC'
78
+ # cyclic(20) #=> 'aaaabaaacaaadaaaeaaa'
79
+ def cyclic(length = nil, alphabet: ASCII_LOWERCASE, n: 4)
80
+ enum = de_bruijn(alphabet: alphabet, n: n)
81
+ r = length.nil? ? enum.to_a : enum.take(length)
82
+ alphabet.is_a?(String) ? r.join : r
83
+ end
102
84
 
103
- pos = 0
104
- saved = []
105
- de_bruijn(alphabet: alphabet, n: n).each do |c|
106
- saved << c
107
- if saved.size > subseq.size
108
- saved.shift
109
- pos += 1
110
- end
111
- return pos if saved == subseq
85
+ # Find the position of a substring in a De Bruijn sequence.
86
+ #
87
+ # @param [String, Array] subseq
88
+ # The substring to be found in the sequence.
89
+ # @param [String, Array] alphabet
90
+ # Alphabet to be used.
91
+ # @param [Integer] n
92
+ # Length of substring that should be unique.
93
+ # Default to +subseq.size+.
94
+ #
95
+ # @return [Integer, nil]
96
+ # The index +subseq+ first appear in the sequence, or +nil+ if not found.
97
+ #
98
+ # @todo Speed! See comment in Python pwntools.
99
+ #
100
+ # @example
101
+ # cyclic_find(cyclic(300)[217, 4]) #=> 217
102
+ def cyclic_find(subseq, alphabet: ASCII_LOWERCASE, n: nil)
103
+ n ||= subseq.size
104
+ subseq = subseq.chars if subseq.is_a?(String)
105
+ return nil unless subseq.all? { |c| alphabet.include?(c) }
106
+
107
+ pos = 0
108
+ saved = []
109
+ de_bruijn(alphabet: alphabet, n: n).each do |c|
110
+ saved << c
111
+ if saved.size > subseq.size
112
+ saved.shift
113
+ pos += 1
112
114
  end
113
- nil
115
+ return pos if saved == subseq
114
116
  end
117
+ nil
115
118
  end
116
-
117
- extend ClassMethods
118
119
  end
119
120
  end
120
121
  end