pwntools 0.1.0 → 1.0.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 (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