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,220 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'logger'
4
+
5
+ # TODO(Darkpi): Check if there should be special care for threading.
6
+
7
+ module Pwnlib
8
+ # Context module, store some platform-dependent informations.
9
+ module Context
10
+ # The type for context. User should never need to initialize one by themself.
11
+ class ContextType
12
+ DEFAULT = {
13
+ arch: 'i386',
14
+ bits: 32,
15
+ endian: 'little',
16
+ log_level: Logger::INFO,
17
+ newline: "\n",
18
+ os: 'linux',
19
+ signed: false,
20
+ timeout: Float::INFINITY
21
+ }.freeze
22
+
23
+ OSES = %w(linux freebsd windows).sort
24
+
25
+ BIG_32 = { endian: 'big', bits: 32 }.freeze
26
+ BIG_64 = { endian: 'big', bits: 64 }.freeze
27
+ LITTLE_8 = { endian: 'little', bits: 8 }.freeze
28
+ LITTLE_16 = { endian: 'little', bits: 16 }.freeze
29
+ LITTLE_32 = { endian: 'little', bits: 32 }.freeze
30
+ LITTLE_64 = { endian: 'little', bits: 64 }.freeze
31
+
32
+ class << self
33
+ def longest(d)
34
+ Hash[d.sort_by { |k, _v| k.size }.reverse]
35
+ end
36
+ private :longest
37
+ end
38
+
39
+ ARCHS = longest(
40
+ 'aarch64' => LITTLE_64,
41
+ 'alpha' => LITTLE_64,
42
+ 'avr' => LITTLE_8,
43
+ 'amd64' => LITTLE_64,
44
+ 'arm' => LITTLE_32,
45
+ 'cris' => LITTLE_32,
46
+ 'i386' => LITTLE_32,
47
+ 'ia64' => BIG_64,
48
+ 'm68k' => BIG_32,
49
+ 'mips' => LITTLE_32,
50
+ 'mips64' => LITTLE_64,
51
+ 'msp430' => LITTLE_16,
52
+ 'powerpc' => BIG_32,
53
+ 'powerpc64' => BIG_64,
54
+ 's390' => BIG_32,
55
+ 'sparc' => BIG_32,
56
+ 'sparc64' => BIG_64,
57
+ 'thumb' => LITTLE_32,
58
+ 'vax' => LITTLE_32
59
+ )
60
+
61
+ ENDIANNESSES = longest(
62
+ 'be' => 'big',
63
+ 'eb' => 'big',
64
+ 'big' => 'big',
65
+ 'le' => 'little',
66
+ 'el' => 'little',
67
+ 'little' => 'little'
68
+ )
69
+
70
+ SIGNEDNESSES = {
71
+ 'unsigned' => false,
72
+ 'no' => false,
73
+ 'yes' => true,
74
+ 'signed' => true
75
+ }.freeze
76
+
77
+ VALID_SIGNED = SIGNEDNESSES.keys
78
+
79
+ # XXX(Darkpi): Should we just hard-coded all levels here,
80
+ # or should we use Logger#const_defined?
81
+ # (This would include constant SEV_LEVEL, and exclude UNKNOWN)?
82
+ LOG_LEVELS = %w(DEBUG INFO WARN ERROR FATAL UNKNOWN).freeze
83
+
84
+ def initialize(**kwargs)
85
+ @attrs = DEFAULT.dup
86
+ update(**kwargs)
87
+ end
88
+
89
+ def update(**kwargs)
90
+ kwargs.each do |k, v|
91
+ next if v.nil?
92
+ public_send("#{k}=", v)
93
+ end
94
+ self
95
+ end
96
+
97
+ alias [] update
98
+ alias call update
99
+
100
+ def to_s
101
+ vals = @attrs.map { |k, v| "#{k} = #{v.inspect}" }
102
+ "#{self.class}(#{vals.join(', ')})"
103
+ end
104
+
105
+ # This would return what the block return.
106
+ def local(**kwargs)
107
+ raise ArgumentError, "Need a block for #{self.class}##{__callee__}" unless block_given?
108
+ # XXX(Darkpi): improve performance for this if this is too slow, since we use this in many
109
+ # places that has argument endian / signed / ...
110
+ old_attrs = @attrs.dup
111
+ begin
112
+ update(**kwargs)
113
+ yield
114
+ ensure
115
+ @attrs = old_attrs
116
+ end
117
+ end
118
+
119
+ def clear
120
+ @attrs = DEFAULT.dup
121
+ end
122
+
123
+ # Getters here.
124
+ DEFAULT.each_key do |k|
125
+ define_method(k) { @attrs[k] }
126
+ end
127
+
128
+ def newline=(newline)
129
+ @attrs[:newline] = newline
130
+ end
131
+
132
+ # TODO(Darkpi): Timeout module.
133
+ def timeout=(timeout)
134
+ @attrs[:timeout] = timeout
135
+ end
136
+
137
+ # Difference from Python pwntools:
138
+ # We always change +bits+ and +endian+ field whether user have already changed them.
139
+ def arch=(arch)
140
+ arch = arch.downcase.gsub(/[[:punct:]]/, '')
141
+ defaults = ARCHS[arch]
142
+ raise ArgumentError, "arch must be one of #{ARCHS.keys.sort.inspect}" unless defaults
143
+ defaults.each { |k, v| @attrs[k] = v }
144
+ @attrs[:arch] = arch
145
+ end
146
+
147
+ def bits=(bits)
148
+ raise ArgumentError, "bits must be > 0 (#{bits} given)" unless bits > 0
149
+ @attrs[:bits] = bits
150
+ end
151
+
152
+ def bytes
153
+ bits / 8
154
+ end
155
+
156
+ def bytes=(bytes)
157
+ self.bits = bytes * 8
158
+ end
159
+
160
+ def endian=(endian)
161
+ endian = ENDIANNESSES[endian.downcase]
162
+ raise ArgumentError, "endian must be one of #{ENDIANNESSES.sort.inspect}" if endian.nil?
163
+ @attrs[:endian] = endian
164
+ end
165
+
166
+ def log_level=(value)
167
+ log_level = nil
168
+ case value
169
+ when String
170
+ value = value.upcase
171
+ log_level = Logger.const_get(value) if LOG_LEVELS.include?(value)
172
+ when Integer
173
+ log_level = value
174
+ end
175
+ raise ArgumentError, "log_level must be an integer or one of #{LOG_LEVELS.inspect}" unless log_level
176
+ @attrs[:log_level] = log_level
177
+ end
178
+
179
+ def os=(os)
180
+ os = os.downcase
181
+ raise ArgumentError, "os must be one of #{OSES.sort.inspect}" unless OSES.include?(os)
182
+ @attrs[:os] = os
183
+ end
184
+
185
+ def signed=(value)
186
+ signed = nil
187
+ case value
188
+ when String
189
+ signed = SIGNEDNESSES[value.downcase]
190
+ when true, false
191
+ signed = value
192
+ end
193
+ if signed.nil?
194
+ raise ArgumentError, "signed must be boolean or one of #{SIGNEDNESSES.keys.sort.inspect}"
195
+ end
196
+ @attrs[:signed] = signed
197
+ end
198
+
199
+ # TODO(Darkpi): #binary when we can read ELF.
200
+ end
201
+
202
+ @context = ContextType.new
203
+
204
+ class << self
205
+ attr_reader :context
206
+ end
207
+
208
+ # For include.
209
+ # @!visibility private
210
+ def context
211
+ ::Pwnlib::Context.context
212
+ end
213
+
214
+ # @!visibility private
215
+ def self.included(base)
216
+ # XXX(Darkpi): Should we do this?
217
+ base.__send__(:private, :context)
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,110 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/context'
4
+ require 'pwnlib/memleak'
5
+ require 'pwnlib/util/packing'
6
+
7
+ # TODO(hh): Use ELF datatype instead of magic offset
8
+
9
+ module Pwnlib
10
+ # DynELF class, resolve symbols in loaded, dynamically-linked ELF binaries.
11
+ # Given a function which can leak data at an arbitrary address,
12
+ # any symbol in any loaded library can be resolved.
13
+ class DynELF
14
+ PT_DYNAMIC = 2
15
+ DT_GNU_HASH = 0x6ffffef5
16
+ DT_HASH = 4
17
+ DT_STRTAB = 5
18
+ DT_SYMTAB = 6
19
+
20
+ attr_reader :libbase
21
+
22
+ def initialize(addr, &block)
23
+ @leak = ::Pwnlib::MemLeak.new(&block)
24
+ @libbase = @leak.find_elf_base(addr)
25
+ @elfclass = { "\x01" => 32, "\x02" => 64 }[@leak.b(@libbase + 4)]
26
+ @elfword = @elfclass / 8
27
+ @unp = ->(x) { Util::Packing.public_send({ 32 => :u32, 64 => :u64 }[@elfclass], x) }
28
+ @dynamic = find_dynamic
29
+ @hshtab = @strtab = @symtab = nil
30
+ end
31
+
32
+ def lookup(symb)
33
+ @hshtab ||= find_dt(DT_GNU_HASH)
34
+ @strtab ||= find_dt(DT_STRTAB)
35
+ @symtab ||= find_dt(DT_SYMTAB)
36
+ resolve_symbol_gnu(symb)
37
+ end
38
+
39
+ private
40
+
41
+ # Function used to generated GNU-style hashes for strings.
42
+ def gnu_hash(s)
43
+ s.bytes.reduce(5381) { |acc, elem| (acc * 33 + elem) & 0xffffffff }
44
+ end
45
+
46
+ def find_dynamic
47
+ e_phoff_offset = { 32 => 28, 64 => 32 }[@elfclass]
48
+ e_phoff = @libbase + @unp.call(@leak.n(@libbase + e_phoff_offset, @elfword))
49
+ phdr_size = { 32 => 32, 64 => 56 }[@elfclass]
50
+ loop do
51
+ ptype = @leak.d(e_phoff)
52
+ break if ptype == PT_DYNAMIC
53
+ e_phoff += phdr_size
54
+ end
55
+ offset = { 32 => 8, 64 => 16 }[@elfclass]
56
+ dyn = @unp.call(@leak.n(e_phoff + offset, @elfword))
57
+ # Sometimes this is an offset instead of an address
58
+ dyn += @libbase if (0...0x400000).cover?(dyn)
59
+ dyn
60
+ end
61
+
62
+ def find_dt(tag)
63
+ dyn_size = @elfword * 2
64
+ ptr = @dynamic
65
+ loop do
66
+ tmp = @leak.n(ptr, @elfword * 2)
67
+ d_tag = @unp.call(tmp[0, @elfword])
68
+ d_addr = @unp.call(tmp[@elfword, @elfword])
69
+ break if d_tag.zero?
70
+ return d_addr if tag == d_tag
71
+ ptr += dyn_size
72
+ end
73
+ nil
74
+ end
75
+
76
+ def resolve_symbol_gnu(symb)
77
+ sym_size = { 32 => 16, 64 => 24 }[@elfclass]
78
+ # Leak GNU_HASH section header
79
+ nbuckets = @leak.d(@hshtab)
80
+ symndx = @leak.d(@hshtab + 4)
81
+ maskwords = @leak.d(@hshtab + 8)
82
+
83
+ l_gnu_buckets = @hshtab + 16 + (@elfword * maskwords)
84
+ l_gnu_chain_zero = l_gnu_buckets + (4 * nbuckets) - (4 * symndx)
85
+
86
+ hsh = gnu_hash(symb)
87
+ bucket = hsh % nbuckets
88
+
89
+ i = @leak.d(l_gnu_buckets + bucket * 4)
90
+ return nil if i.zero?
91
+
92
+ hsh2 = 0
93
+ while (hsh2 & 1).zero?
94
+ hsh2 = @leak.d(l_gnu_chain_zero + i * 4)
95
+ if ((hsh ^ hsh2) >> 1).zero?
96
+ sym = @symtab + sym_size * i
97
+ st_name = @leak.d(sym)
98
+ name = @leak.n(@strtab + st_name, symb.length + 1)
99
+ if name == (symb + "\x00")
100
+ offset = { 32 => 4, 64 => 8 }[@elfclass]
101
+ st_value = @unp.call(@leak.n(sym + offset, @elfword))
102
+ return @libbase + st_value
103
+ end
104
+ end
105
+ i += 1
106
+ end
107
+ nil
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/ext/helper'
4
+ require 'pwnlib/util/fiddling'
5
+ require 'pwnlib/util/packing'
6
+
7
+ module Pwnlib
8
+ module Ext
9
+ module Array
10
+ # Methods to be mixed into Array.
11
+ module InstanceMethods
12
+ extend ::Pwnlib::Ext::Helper
13
+
14
+ def_proxy_method ::Pwnlib::Util::Packing, %w(flat)
15
+ def_proxy_method ::Pwnlib::Util::Fiddling, %w(unbits)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ ::Array.public_send(:include, ::Pwnlib::Ext::Array::InstanceMethods)
@@ -0,0 +1,21 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ module Pwnlib
4
+ module Ext
5
+ # Helper methods for defining extension
6
+ module Helper
7
+ def def_proxy_method(mod, *ms, **m2)
8
+ ms.flatten
9
+ .map { |x| [x, x] }
10
+ .concat(m2.to_a)
11
+ .each do |method, proxy_to|
12
+ class_eval <<-EOS
13
+ def #{method}(*args, &block)
14
+ #{mod}.#{proxy_to}(self, *args, &block)
15
+ end
16
+ EOS
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/ext/helper'
4
+ require 'pwnlib/util/packing'
5
+
6
+ module Pwnlib
7
+ module Ext
8
+ module Integer
9
+ # Methods to be mixed into Integer.
10
+ module InstanceMethods
11
+ extend ::Pwnlib::Ext::Helper
12
+
13
+ def_proxy_method ::Pwnlib::Util::Packing, %w(pack p8 p16 p32 p64)
14
+ def_proxy_method ::Pwnlib::Util::Fiddling, %w(bits bits_str), bitswap: 'bitswap_int'
15
+ def_proxy_method ::Pwnlib::Util::Fiddling, %w(hex)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ ::Integer.public_send(:include, ::Pwnlib::Ext::Integer::InstanceMethods)
@@ -0,0 +1,23 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/ext/helper'
4
+ require 'pwnlib/util/fiddling'
5
+ require 'pwnlib/util/packing'
6
+
7
+ module Pwnlib
8
+ module Ext
9
+ module String
10
+ # Methods to be mixed into String.
11
+ module InstanceMethods
12
+ extend ::Pwnlib::Ext::Helper
13
+
14
+ def_proxy_method ::Pwnlib::Util::Packing, %w(unpack unpack_many u8 u16 u32 u64)
15
+ def_proxy_method ::Pwnlib::Util::Fiddling, %w(
16
+ enhex unhex urlencode urldecode bits bits_str unbits bitswap b64e b64d
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ ::String.public_send(:include, ::Pwnlib::Ext::String::InstanceMethods)
@@ -0,0 +1,61 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/util/packing'
4
+
5
+ module Pwnlib
6
+ # MemLeak is a caching and heuristic tool for exploiting memory leaks.
7
+ class MemLeak
8
+ PAGE_SIZE = 0x1000
9
+ PAGE_MASK = ~(PAGE_SIZE - 1)
10
+
11
+ def initialize(&block)
12
+ @leak = block
13
+ @base = nil
14
+ @cache = {}
15
+ end
16
+
17
+ def find_elf_base(ptr)
18
+ ptr &= PAGE_MASK
19
+ loop do
20
+ return @base = ptr if n(ptr, 4) == "\x7fELF"
21
+ ptr -= PAGE_SIZE
22
+ end
23
+ end
24
+
25
+ # Call the leaker function on address `addr`.
26
+ # Store the result to @cache
27
+ def do_leak(addr)
28
+ unless @cache.key?(addr)
29
+ data = @leak.call(addr)
30
+ data.bytes.each.with_index(addr) { |b, i| @cache[i] = b }
31
+ end
32
+ @cache[addr]
33
+ end
34
+
35
+ # Leak `numb` bytes at `addr`.
36
+ # Returns a string with the leaked bytes.
37
+ def n(addr, numb)
38
+ (0...numb).map { |i| do_leak(addr + i) }.pack('C*')
39
+ end
40
+
41
+ # Leak byte at ``((uint8_t*) addr)[ndx]``
42
+ def b(addr)
43
+ n(addr, 1)
44
+ end
45
+
46
+ # Leak word at ``((uint16_t*) addr)[ndx]``
47
+ def w(addr)
48
+ Util::Packing.u16(n(addr, 2))
49
+ end
50
+
51
+ # Leak dword at ``((uint32_t*) addr)[ndx]``
52
+ def d(addr)
53
+ Util::Packing.u32(n(addr, 4))
54
+ end
55
+
56
+ # Leak qword at ``((uint64_t*) addr)[ndx]``
57
+ def q(addr)
58
+ Util::Packing.u64(n(addr, 8))
59
+ end
60
+ end
61
+ end