pwntools 0.1.0

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