pwntools 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.
@@ -0,0 +1,26 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ # require this file would also require all things in pwnlib, but would not
4
+ # pollute anything.
5
+
6
+ require 'pwnlib/constants/constant'
7
+ require 'pwnlib/constants/constants'
8
+ require 'pwnlib/context'
9
+ require 'pwnlib/dynelf'
10
+ require 'pwnlib/reg_sort'
11
+
12
+ require 'pwnlib/util/cyclic'
13
+ require 'pwnlib/util/fiddling'
14
+ require 'pwnlib/util/hexdump'
15
+ require 'pwnlib/util/packing'
16
+
17
+ # include this module in a class to use all pwnlib functions in that class
18
+ # instance.
19
+ module Pwn
20
+ include ::Pwnlib::Context
21
+
22
+ include ::Pwnlib::Util::Cyclic::ClassMethods
23
+ include ::Pwnlib::Util::Fiddling::ClassMethods
24
+ include ::Pwnlib::Util::HexDump::ClassMethods
25
+ include ::Pwnlib::Util::Packing::ClassMethods
26
+ end
@@ -0,0 +1,147 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/context'
4
+
5
+ module Pwnlib
6
+ # Do topological sort on register assignments.
7
+ module RegSort
8
+ # @note Do not create and call instance method here. Instead, call module method on {RegSort}.
9
+ module ClassMethods
10
+ # Sorts register dependencies.
11
+ #
12
+ # Given a dictionary of registers to desired register contents,
13
+ # return the optimal order in which to set the registers to
14
+ # those contents.
15
+ #
16
+ # The implementation assumes that it is possible to move from
17
+ # any register to any other register.
18
+ #
19
+ # @param [Hash<Symbol, String => Object>] in_out
20
+ # Dictionary of desired register states.
21
+ # Keys are registers, values are either registers or any other value.
22
+ # @param [Array<String>] all_regs
23
+ # List of all possible registers.
24
+ # Used to determine which values in +in_out+ are registers, versus
25
+ # regular values.
26
+ # @option [Boolean] randomize
27
+ # Randomize as much as possible about the order or registers.
28
+ #
29
+ # @return [Array]
30
+ # Array of instructions, see examples for more details.
31
+ #
32
+ # @example
33
+ # regs = %w(a b c d x y z)
34
+ # regsort({a: 1, b: 2}, regs)
35
+ # => [['mov', 'a', 1], ['mov', 'b', 2]]
36
+ # regsort({a: 'b', b: 'a'}, regs)
37
+ # => [['xchg', 'a', 'b']]
38
+ # regsort({a: 1, b: 'a'}, regs)
39
+ # => [['mov', 'b', 'a'], ['mov', 'a', 1]]
40
+ # regsort({a: 'b', b: 'a', c: 3}, regs)
41
+ # => [['mov', 'c', 3], ['xchg', 'a', 'b']]
42
+ # regsort({a: 'b', b: 'a', c: 'b'}, regs)
43
+ # => [['mov', 'c', 'b'], ['xchg', 'a', 'b']]
44
+ # regsort({a: 'b', b: 'c', c: 'a', x: '1', y: 'z', z: 'c'}, regs)
45
+ # => [['mov', 'x', '1'],
46
+ # ['mov', 'y', 'z'],
47
+ # ['mov', 'z', 'c'],
48
+ # ['xchg', 'a', 'b'],
49
+ # ['xchg', 'b', 'c']]
50
+ #
51
+ # @note
52
+ # Different from python-pwntools, we don't support +tmp+/+xchg+ options
53
+ # because there's no such usage at all.
54
+ def regsort(in_out, all_regs, randomize: nil)
55
+ # randomize = context.randomize if randomize.nil?
56
+
57
+ # TODO(david942j): stringify_keys
58
+ in_out = in_out.map { |k, v| [k.to_s, v] }.to_h
59
+ # Drop all registers which will be set to themselves.
60
+ # Ex. {eax: 'eax'}
61
+ in_out.reject! { |k, v| k == v }
62
+
63
+ # Check input
64
+ if (in_out.keys - all_regs).any?
65
+ raise ArgumentError, format('Unknown register! Know: %p. Got: %p', all_regs, in_out)
66
+ end
67
+
68
+ # Collapse constant values
69
+ #
70
+ # Ex. {eax: 1, ebx: 1} can be collapsed to {eax: 1, ebx: 'eax'}.
71
+ # +post_mov+ are collapsed registers, set their values in the end.
72
+ post_mov = in_out.group_by { |_, v| v }.each_value.with_object({}) do |list, hash|
73
+ list.sort!
74
+ first_reg, val = list.shift
75
+ # Special case for val.zero? because zeroify registers cost cheaper than mov.
76
+ next if list.empty? || all_regs.include?(val) || val.zero?
77
+ list.each do |reg, _|
78
+ hash[reg] = first_reg
79
+ in_out.delete(reg)
80
+ end
81
+ end
82
+
83
+ graph = in_out.dup
84
+ result = []
85
+
86
+ # Let's do the topological sort.
87
+ # so sad ruby 2.1 doesn't have +itself+...
88
+ deg = graph.values.group_by { |i| i }.map { |k, v| [k, v.size] }.to_h
89
+ graph.each_key { |k| deg[k] ||= 0 }
90
+
91
+ until deg.empty?
92
+ min_deg = deg.min_by { |_, v| v }[1]
93
+ break unless min_deg.zero? # remain are all cycles
94
+ min_pivs = deg.select { |_, v| v == min_deg }
95
+ piv = randomize ? min_pivs.sample : min_pivs.first
96
+ dst = piv.first
97
+ deg.delete(dst)
98
+ next unless graph.key?(dst) # Reach an end node.
99
+ deg[graph[dst]] -= 1
100
+ result << ['mov', dst, graph[dst]]
101
+ graph.delete(dst)
102
+ end
103
+
104
+ # Remain must be cycles.
105
+ graph.each_key do |reg|
106
+ cycle = check_cycle(reg, graph)
107
+ cycle.each_cons(2) do |d, s|
108
+ result << ['xchg', d, s]
109
+ end
110
+ cycle.each { |r| graph.delete(r) }
111
+ end
112
+
113
+ # Now assign those collapsed registers.
114
+ post_mov.sort.each do |dreg, sreg|
115
+ result << ['mov', dreg, sreg]
116
+ end
117
+
118
+ result
119
+ end
120
+
121
+ private
122
+
123
+ # Walk down the assignment list of a register,
124
+ # return the path walked if it is encountered again.
125
+ # @example
126
+ # check_cycle('a', {'a' => 1}) #=> []
127
+ # check_cycle('a', {'a' => 'a'}) #=> ['a']
128
+ # check_cycle('a', {'a' => 'b', 'b' => 'c', 'c' => 'b', 'd' => 'a'}) #=> []
129
+ # check_cycle('a', {'a' => 'b', 'b' => 'c', 'c' => 'd', 'd' => 'a'})
130
+ # #=> ['a', 'b', 'c', 'd']
131
+ def check_cycle(reg, assignments)
132
+ check_cycle_(reg, assignments, [])
133
+ end
134
+
135
+ def check_cycle_(reg, assignments, path) # :nodoc:
136
+ target = assignments[reg]
137
+ path << reg
138
+ # No cycle, some other value (e.g. 1)
139
+ return [] unless assignments.key?(target)
140
+ # Found a cycle
141
+ return target == path.first ? path : [] if path.include?(target)
142
+ check_cycle_(target, assignments, path)
143
+ end
144
+ end
145
+ extend ClassMethods
146
+ end
147
+ end
@@ -0,0 +1,120 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ module Pwnlib
4
+ module Util
5
+ # Generate string with easy-to-find pattern.
6
+ # See {ClassMethods} for method details.
7
+ # @example Call by specifying full module path.
8
+ # require 'pwnlib/util/cyclic'
9
+ # Pwnlib::Util::Cyclic.cyclic_find(Pwnlib::Util::Cyclic.cyclic(200)[123, 4]) #=> 123
10
+ # @example require 'pwn' and have all methods.
11
+ # require 'pwn'
12
+ # cyclic_find(cyclic(200)[123, 4]) #=> 123
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
19
+
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)
43
+
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
54
+ end
55
+ end
56
+
57
+ db[1, 1]
58
+ end
59
+
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
81
+
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) }
102
+
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
112
+ end
113
+ nil
114
+ end
115
+ end
116
+
117
+ extend ClassMethods
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,262 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/context'
4
+
5
+ module Pwnlib
6
+ module Util
7
+ # Some fiddling methods.
8
+ # See {ClassMethods} for method details.
9
+ # @example Call by specifying full module path.
10
+ # require 'pwnlib/util/fiddling'
11
+ # Pwnlib::Util::Fiddling.enhex('217') #=> '323137'
12
+ # @example require 'pwn' and have all methods.
13
+ # require 'pwn'
14
+ # enhex('217') #=> '323137'
15
+ module Fiddling
16
+ # @note Do not create and call instance method here. Instead, call module method on {Fiddling}.
17
+ module ClassMethods
18
+ # Hex-encodes a string.
19
+ #
20
+ # @param [String] s
21
+ # String to be encoded.
22
+ # @return [String]
23
+ # Hex-encoded string.
24
+ #
25
+ # @example
26
+ # enhex('217') #=> '323137'
27
+ def enhex(s)
28
+ s.unpack('H*')[0]
29
+ end
30
+
31
+ # Hex-decodes a string.
32
+ #
33
+ # @param [String] s
34
+ # String to be decoded.
35
+ # @return [String]
36
+ # Hex-decoded string.
37
+ #
38
+ # @example
39
+ # unhex('353134') #=> '514'
40
+ def unhex(s)
41
+ [s].pack('H*')
42
+ end
43
+
44
+ # Present number in hex format, same as python hex() do.
45
+ #
46
+ # @param [Integer] n
47
+ # The number.
48
+ #
49
+ # @return [String]
50
+ # The hex format string.
51
+ #
52
+ # @example
53
+ # hex(0) #=> '0x0'
54
+ # hex(-10) #=> '-0xa'
55
+ # hex(0xfaceb00cdeadbeef) #=> '0xfaceb00cdeadbeef'
56
+ def hex(n)
57
+ (n < 0 ? '-' : '') + format('0x%x', n.abs)
58
+ end
59
+
60
+ # URL-encodes a string.
61
+ #
62
+ # @param [String] s
63
+ # String to be encoded.
64
+ # @return [String]
65
+ # URL-encoded string.
66
+ #
67
+ # @example
68
+ # urlencode('shikway') #=> '%73%68%69%6b%77%61%79'
69
+ def urlencode(s)
70
+ s.bytes.map { |b| format('%%%02x', b) }.join
71
+ end
72
+
73
+ # URL-decodes a string.
74
+ #
75
+ # @param [String] s
76
+ # String to be decoded.
77
+ # @param [Boolean] ignore_invalid
78
+ # Whether invalid encoding should be ignore.
79
+ # If set to +true+,
80
+ # invalid encoding in input are left intact to output.
81
+ # @return [String]
82
+ # URL-decoded string.
83
+ # @raise [ArgumentError]
84
+ # If +ignore_invalid+ is +false+,
85
+ # and there are invalid encoding in input.
86
+ #
87
+ # @example
88
+ # urldecode('test%20url') #=> 'test url'
89
+ # urldecode('%qw%er%ty') #=> raise ArgumentError
90
+ # urldecode('%qw%er%ty', true) #=> '%qw%er%ty'
91
+ def urldecode(s, ignore_invalid = false)
92
+ res = ''
93
+ n = 0
94
+ while n < s.size
95
+ if s[n] != '%'
96
+ res << s[n]
97
+ n += 1
98
+ else
99
+ cur = s[n + 1, 2]
100
+ if cur =~ /[0-9a-fA-F]{2}/
101
+ res << cur.to_i(16).chr
102
+ n += 3
103
+ elsif ignore_invalid
104
+ res << '%'
105
+ n += 1
106
+ else
107
+ raise ArgumentError, 'Invalid input to urldecode'
108
+ end
109
+ end
110
+ end
111
+ res
112
+ end
113
+
114
+ # Converts the argument to an array of bits.
115
+ #
116
+ # @param [String, Integer] s
117
+ # Input to be converted into bits.
118
+ # If input is integer,
119
+ # output would be padded to byte aligned.
120
+ # @param [String] endian
121
+ # Endian for conversion.
122
+ # Can be any value accepted by context (See {Context::ContextType}).
123
+ # @param zero
124
+ # Object representing a 0-bit.
125
+ # @param one
126
+ # Object representing a 1-bit.
127
+ # @return [Array]
128
+ # An array consisting of +zero+ and +one+.
129
+ #
130
+ # @example
131
+ # bits(314) #=> [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0]
132
+ # bits('orz', zero: '-', one: '+').join #=> '-++-++++-+++--+--++++-+-'
133
+ # bits(128, endian: 'little') #=> [0, 0, 0, 0, 0, 0, 0, 1]
134
+ def bits(s, endian: 'big', zero: 0, one: 1)
135
+ context.local(endian: endian) do
136
+ is_little = context.endian == 'little'
137
+ case s
138
+ when String
139
+ v = 'B*'
140
+ v.downcase! if is_little
141
+ s.unpack(v)[0].chars.map { |ch| ch == '1' ? one : zero }
142
+ when Integer
143
+ # TODO(Darkpi): What should we do to negative number?
144
+ raise ArgumentError, 's must be non-negative' unless s >= 0
145
+ r = s.to_s(2).chars.map { |ch| ch == '1' ? one : zero }
146
+ r.unshift(zero) until (r.size % 8).zero?
147
+ is_little ? r.reverse : r
148
+ else
149
+ raise ArgumentError, 's must be either String or Integer'
150
+ end
151
+ end
152
+ end
153
+
154
+ # Simple wrapper around {#bits}, which converts output to string.
155
+ #
156
+ # @param (see #bits)
157
+ # @return [String]
158
+ #
159
+ # @example
160
+ # bits_str('GG') #=> '0100011101000111'
161
+ def bits_str(s, endian: 'big', zero: 0, one: 1)
162
+ bits(s, endian: endian, zero: zero, one: one).join
163
+ end
164
+
165
+ # Reverse of {#bits} and {#bits_str}, convert an array of bits back to string.
166
+ #
167
+ # @param [String, Array<String, Integer, Boolean>] s
168
+ # String or array of bits to be convert back to string.
169
+ # <tt>[0, '0', false]</tt> represents 0-bit,
170
+ # and <tt>[1, '1', true]</tt> represents 1-bit.
171
+ # @param [String] endian
172
+ # Endian for conversion.
173
+ # Can be any value accepted by context (See {Context::ContextType}).
174
+ # @raise [ArgumentError]
175
+ # If input contains value not in <tt>[0, 1, '0', '1', true, false]</tt>.
176
+ #
177
+ # @example
178
+ # unbits('0100011101000111') #=> 'GG'
179
+ # unbits([0, 1, 0, 1, 0, 1, 0, 0]) #=> 'T'
180
+ # unbits('0100011101000111', endian: 'little') #=> "\xE2\xE2"
181
+ def unbits(s, endian: 'big')
182
+ s = s.chars if s.is_a?(String)
183
+ context.local(endian: endian) do
184
+ is_little = context.endian == 'little'
185
+ bytes = s.map do |c|
186
+ case c
187
+ when '1', 1, true then '1'
188
+ when '0', 0, false then '0'
189
+ else raise ArgumentError, "cannot decode value #{c.inspect} into a bit"
190
+ end
191
+ end
192
+ [bytes.join].pack(is_little ? 'b*' : 'B*')
193
+ end
194
+ end
195
+
196
+ # Reverse the bits of each byte in input string.
197
+ #
198
+ # @param [String] s
199
+ # Input string.
200
+ # @return [String]
201
+ # The string with bits of each byte reversed.
202
+ #
203
+ # @example
204
+ # bitswap('rb') #=> 'NF'
205
+ def bitswap(s)
206
+ unbits(bits(s, endian: 'big'), endian: 'little')
207
+ end
208
+
209
+ # Reverse the bits of a number, and returns the result as number.
210
+ #
211
+ # @param [Integer] n
212
+ # @param [Integer] bits
213
+ # The bit length of +n+,
214
+ # only the lower +bits+ bits of +n+ would be used.
215
+ # Default to context.bits
216
+ # @return [Integer]
217
+ # The number with bits reversed.
218
+ #
219
+ # @example
220
+ # bitswap_int(217, bits: 8) #=> 155
221
+ def bitswap_int(n, bits: nil)
222
+ context.local(bits: bits) do
223
+ bits = context.bits
224
+ n &= (1 << bits) - 1
225
+ bits_str(n, endian: 'little').ljust(bits, '0').to_i(2)
226
+ end
227
+ end
228
+
229
+ # Base64-encodes a string.
230
+ # Do NOT contains those stupid newline (with RFC 4648)
231
+ #
232
+ # @param [String] s
233
+ # String to be encoded.
234
+ # @return [String]
235
+ # Base64-encoded string.
236
+ #
237
+ # @example
238
+ # b64e('desu') #=> 'ZGVzdQ=='
239
+ def b64e(s)
240
+ [s].pack('m0')
241
+ end
242
+
243
+ # Base64-decodes a string.
244
+ #
245
+ # @param [String] s
246
+ # String to be decoded.
247
+ # @return [String]
248
+ # Base64-decoded string.
249
+ #
250
+ # @example
251
+ # b64d('ZGVzdQ==') #=> 'desu'
252
+ def b64d(s)
253
+ s.unpack('m0')[0]
254
+ end
255
+
256
+ include ::Pwnlib::Context
257
+ end
258
+
259
+ extend ClassMethods
260
+ end
261
+ end
262
+ end