pwntools 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|