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.
- checksums.yaml +7 -0
- data/README.md +49 -0
- data/Rakefile +40 -0
- data/lib/pwn.rb +24 -0
- data/lib/pwnlib/constants/constant.rb +45 -0
- data/lib/pwnlib/constants/constants.rb +82 -0
- data/lib/pwnlib/constants/linux/amd64.rb +1558 -0
- data/lib/pwnlib/constants/linux/i386.rb +1340 -0
- data/lib/pwnlib/context.rb +220 -0
- data/lib/pwnlib/dynelf.rb +110 -0
- data/lib/pwnlib/ext/array.rb +21 -0
- data/lib/pwnlib/ext/helper.rb +21 -0
- data/lib/pwnlib/ext/integer.rb +21 -0
- data/lib/pwnlib/ext/string.rb +23 -0
- data/lib/pwnlib/memleak.rb +61 -0
- data/lib/pwnlib/pwn.rb +26 -0
- data/lib/pwnlib/reg_sort.rb +147 -0
- data/lib/pwnlib/util/cyclic.rb +120 -0
- data/lib/pwnlib/util/fiddling.rb +262 -0
- data/lib/pwnlib/util/hexdump.rb +145 -0
- data/lib/pwnlib/util/packing.rb +284 -0
- data/lib/pwnlib/version.rb +5 -0
- data/test/constants/constant_test.rb +24 -0
- data/test/constants/constants_test.rb +31 -0
- data/test/context_test.rb +131 -0
- data/test/data/victim.c +8 -0
- data/test/data/victim32 +0 -0
- data/test/data/victim64 +0 -0
- data/test/dynelf_test.rb +48 -0
- data/test/ext_test.rb +26 -0
- data/test/files/use_pwn.rb +34 -0
- data/test/files/use_pwnlib.rb +19 -0
- data/test/full_file_test.rb +16 -0
- data/test/memleak_test.rb +72 -0
- data/test/reg_sort_test.rb +41 -0
- data/test/test_helper.rb +13 -0
- data/test/util/cyclic_test.rb +36 -0
- data/test/util/fiddling_test.rb +106 -0
- data/test/util/hexdump_test.rb +179 -0
- data/test/util/packing_test.rb +168 -0
- metadata +231 -0
data/lib/pwnlib/pwn.rb
ADDED
@@ -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
|