pwntools 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +88 -11
- data/Rakefile +5 -1
- data/lib/pwn.rb +9 -7
- data/lib/pwnlib/abi.rb +60 -0
- data/lib/pwnlib/asm.rb +146 -0
- data/lib/pwnlib/constants/constant.rb +16 -2
- data/lib/pwnlib/constants/constants.rb +35 -19
- data/lib/pwnlib/constants/linux/amd64.rb +30 -1
- data/lib/pwnlib/context.rb +25 -17
- data/lib/pwnlib/dynelf.rb +117 -54
- data/lib/pwnlib/elf/elf.rb +267 -0
- data/lib/pwnlib/ext/helper.rb +4 -4
- data/lib/pwnlib/logger.rb +87 -0
- data/lib/pwnlib/memleak.rb +58 -29
- data/lib/pwnlib/pwn.rb +19 -8
- data/lib/pwnlib/reg_sort.rb +102 -108
- data/lib/pwnlib/shellcraft/generators/amd64/common/common.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +17 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb +31 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/mov.rb +127 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/nop.rb +16 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/popad.rb +27 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb +64 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/ret.rb +32 -0
- data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +21 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/linux.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +21 -0
- data/lib/pwnlib/shellcraft/generators/helper.rb +106 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/common.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +17 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/mov.rb +90 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/nop.rb +16 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/pushstr.rb +39 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/linux.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +19 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/common.rb +26 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/infloop.rb +22 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/mov.rb +15 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/pushstr.rb +15 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/pushstr_array.rb +85 -0
- data/lib/pwnlib/shellcraft/generators/x86/common/setregs.rb +82 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/execve.rb +69 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/linux.rb +14 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/ls.rb +66 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/sh.rb +52 -0
- data/lib/pwnlib/shellcraft/generators/x86/linux/syscall.rb +52 -0
- data/lib/pwnlib/shellcraft/registers.rb +145 -0
- data/lib/pwnlib/shellcraft/shellcraft.rb +67 -0
- data/lib/pwnlib/timer.rb +60 -0
- data/lib/pwnlib/tubes/buffer.rb +96 -0
- data/lib/pwnlib/tubes/sock.rb +95 -0
- data/lib/pwnlib/tubes/tube.rb +270 -0
- data/lib/pwnlib/util/cyclic.rb +95 -94
- data/lib/pwnlib/util/fiddling.rb +256 -220
- data/lib/pwnlib/util/getdents.rb +83 -0
- data/lib/pwnlib/util/hexdump.rb +109 -108
- data/lib/pwnlib/util/lists.rb +55 -0
- data/lib/pwnlib/util/packing.rb +226 -228
- data/lib/pwnlib/util/ruby.rb +18 -0
- data/lib/pwnlib/version.rb +2 -1
- data/test/abi_test.rb +21 -0
- data/test/asm_test.rb +104 -0
- data/test/constants/constant_test.rb +1 -0
- data/test/constants/constants_test.rb +4 -2
- data/test/context_test.rb +1 -0
- data/test/data/echo.rb +20 -0
- data/test/data/elfs/Makefile +22 -0
- data/test/data/elfs/amd64.frelro.elf +0 -0
- data/test/data/elfs/amd64.frelro.pie.elf +0 -0
- data/test/data/elfs/amd64.nrelro.elf +0 -0
- data/test/data/elfs/amd64.prelro.elf +0 -0
- data/test/data/elfs/i386.frelro.pie.elf +0 -0
- data/test/data/elfs/i386.prelro.elf +0 -0
- data/test/data/elfs/source.cpp +19 -0
- data/test/data/flag +1 -0
- data/test/data/lib32/ld.so.2 +0 -0
- data/test/data/lib32/libc.so.6 +0 -0
- data/test/data/lib64/ld.so.2 +0 -0
- data/test/data/lib64/libc.so.6 +0 -0
- data/test/dynelf_test.rb +59 -24
- data/test/elf/elf_test.rb +120 -0
- data/test/ext_test.rb +3 -2
- data/test/files/use_pwnlib.rb +1 -1
- data/test/logger_test.rb +61 -0
- data/test/memleak_test.rb +4 -33
- data/test/reg_sort_test.rb +3 -1
- data/test/shellcraft/infloop_test.rb +26 -0
- data/test/shellcraft/linux/ls_test.rb +108 -0
- data/test/shellcraft/linux/sh_test.rb +119 -0
- data/test/shellcraft/linux/syscalls/execve_test.rb +136 -0
- data/test/shellcraft/linux/syscalls/syscall_test.rb +83 -0
- data/test/shellcraft/memcpy_test.rb +35 -0
- data/test/shellcraft/mov_test.rb +98 -0
- data/test/shellcraft/nop_test.rb +26 -0
- data/test/shellcraft/popad_test.rb +29 -0
- data/test/shellcraft/pushstr_array_test.rb +91 -0
- data/test/shellcraft/pushstr_test.rb +108 -0
- data/test/shellcraft/registers_test.rb +32 -0
- data/test/shellcraft/ret_test.rb +30 -0
- data/test/shellcraft/setregs_test.rb +62 -0
- data/test/shellcraft/shellcraft_test.rb +28 -0
- data/test/test_helper.rb +12 -1
- data/test/timer_test.rb +23 -0
- data/test/tubes/buffer_test.rb +45 -0
- data/test/tubes/sock_test.rb +68 -0
- data/test/tubes/tube_test.rb +241 -0
- data/test/util/cyclic_test.rb +2 -1
- data/test/util/fiddling_test.rb +2 -1
- data/test/util/getdents_test.rb +32 -0
- data/test/util/hexdump_test.rb +7 -9
- data/test/util/lists_test.rb +21 -0
- data/test/util/packing_test.rb +4 -3
- metadata +215 -25
@@ -235,7 +235,6 @@ const :__NR_epoll_ctl, 233
|
|
235
235
|
const :__NR_tgkill, 234
|
236
236
|
const :__NR_utimes, 235
|
237
237
|
const :__NR_vserver, 236
|
238
|
-
const :__NR_vserver, 236
|
239
238
|
const :__NR_mbind, 237
|
240
239
|
const :__NR_set_mempolicy, 238
|
241
240
|
const :__NR_get_mempolicy, 239
|
@@ -302,6 +301,36 @@ const :__NR_recvmmsg, 299
|
|
302
301
|
const :__NR_fanotify_init, 300
|
303
302
|
const :__NR_fanotify_mark, 301
|
304
303
|
const :__NR_prlimit64, 302
|
304
|
+
const :__NR_name_to_handle_at, 303
|
305
|
+
const :__NR_open_by_handle_at, 304
|
306
|
+
const :__NR_clock_adjtime, 305
|
307
|
+
const :__NR_syncfs, 306
|
308
|
+
const :__NR_sendmmsg, 307
|
309
|
+
const :__NR_setns, 308
|
310
|
+
const :__NR_getcpu, 309
|
311
|
+
const :__NR_process_vm_readv, 310
|
312
|
+
const :__NR_process_vm_writev, 311
|
313
|
+
const :__NR_kcmp, 312
|
314
|
+
const :__NR_finit_module, 313
|
315
|
+
const :__NR_sched_setattr, 314
|
316
|
+
const :__NR_sched_getattr, 315
|
317
|
+
const :__NR_renameat2, 316
|
318
|
+
const :__NR_seccomp, 317
|
319
|
+
const :__NR_getrandom, 318
|
320
|
+
const :__NR_memfd_create, 319
|
321
|
+
const :__NR_kexec_file_load, 320
|
322
|
+
const :__NR_bpf, 321
|
323
|
+
const :__NR_execveat, 322
|
324
|
+
const :__NR_userfaultfd, 323
|
325
|
+
const :__NR_membarrier, 324
|
326
|
+
const :__NR_mlock2, 325
|
327
|
+
const :__NR_copy_file_range, 326
|
328
|
+
const :__NR_preadv2, 327
|
329
|
+
const :__NR_pwritev2, 328
|
330
|
+
const :__NR_pkey_mprotect, 329
|
331
|
+
const :__NR_pkey_alloc, 330
|
332
|
+
const :__NR_pkey_free, 331
|
333
|
+
const :__NR_statx, 332
|
305
334
|
const :SYS32_restart_syscall, 0
|
306
335
|
const :SYS32_exit, 1
|
307
336
|
const :SYS32_fork, 2
|
data/lib/pwnlib/context.rb
CHANGED
@@ -17,7 +17,7 @@ module Pwnlib
|
|
17
17
|
newline: "\n",
|
18
18
|
os: 'linux',
|
19
19
|
signed: false,
|
20
|
-
timeout:
|
20
|
+
timeout: :forever
|
21
21
|
}.freeze
|
22
22
|
|
23
23
|
OSES = %w(linux freebsd windows).sort
|
@@ -30,10 +30,11 @@ module Pwnlib
|
|
30
30
|
LITTLE_64 = { endian: 'little', bits: 64 }.freeze
|
31
31
|
|
32
32
|
class << self
|
33
|
+
private
|
34
|
+
|
33
35
|
def longest(d)
|
34
36
|
Hash[d.sort_by { |k, _v| k.size }.reverse]
|
35
37
|
end
|
36
|
-
private :longest
|
37
38
|
end
|
38
39
|
|
39
40
|
ARCHS = longest(
|
@@ -76,9 +77,7 @@ module Pwnlib
|
|
76
77
|
|
77
78
|
VALID_SIGNED = SIGNEDNESSES.keys
|
78
79
|
|
79
|
-
#
|
80
|
-
# or should we use Logger#const_defined?
|
81
|
-
# (This would include constant SEV_LEVEL, and exclude UNKNOWN)?
|
80
|
+
# We use Logger#const_defined for pwnlib logger.
|
82
81
|
LOG_LEVELS = %w(DEBUG INFO WARN ERROR FATAL UNKNOWN).freeze
|
83
82
|
|
84
83
|
def initialize(**kwargs)
|
@@ -105,8 +104,9 @@ module Pwnlib
|
|
105
104
|
# This would return what the block return.
|
106
105
|
def local(**kwargs)
|
107
106
|
raise ArgumentError, "Need a block for #{self.class}##{__callee__}" unless block_given?
|
108
|
-
# XXX(Darkpi):
|
109
|
-
#
|
107
|
+
# XXX(Darkpi):
|
108
|
+
# improve performance for this if this is too slow, since we use this in many places that has argument
|
109
|
+
# endian / signed / ...
|
110
110
|
old_attrs = @attrs.dup
|
111
111
|
begin
|
112
112
|
update(**kwargs)
|
@@ -129,13 +129,11 @@ module Pwnlib
|
|
129
129
|
@attrs[:newline] = newline
|
130
130
|
end
|
131
131
|
|
132
|
-
# TODO(Darkpi): Timeout module.
|
133
132
|
def timeout=(timeout)
|
134
133
|
@attrs[:timeout] = timeout
|
135
134
|
end
|
136
135
|
|
137
|
-
#
|
138
|
-
# We always change +bits+ and +endian+ field whether user have already changed them.
|
136
|
+
# @diff We always change +bits+ and +endian+ field whether user have already changed them.
|
139
137
|
def arch=(arch)
|
140
138
|
arch = arch.downcase.gsub(/[[:punct:]]/, '')
|
141
139
|
defaults = ARCHS[arch]
|
@@ -166,8 +164,8 @@ module Pwnlib
|
|
166
164
|
def log_level=(value)
|
167
165
|
log_level = nil
|
168
166
|
case value
|
169
|
-
when String
|
170
|
-
value = value.upcase
|
167
|
+
when String, Symbol
|
168
|
+
value = value.to_s.upcase
|
171
169
|
log_level = Logger.const_get(value) if LOG_LEVELS.include?(value)
|
172
170
|
when Integer
|
173
171
|
log_level = value
|
@@ -201,20 +199,30 @@ module Pwnlib
|
|
201
199
|
|
202
200
|
@context = ContextType.new
|
203
201
|
|
202
|
+
# @!attribute [r] context
|
203
|
+
# @return [ContextType] the singleton context for all class.
|
204
204
|
class << self
|
205
205
|
attr_reader :context
|
206
206
|
end
|
207
207
|
|
208
|
-
#
|
208
|
+
# A module for include hook for context.
|
209
|
+
# Including Pwnlib::Context from module M would add +context+ as a private instance method and a private class
|
210
|
+
# method for module M.
|
209
211
|
# @!visibility private
|
210
|
-
|
211
|
-
|
212
|
+
module IncludeContext
|
213
|
+
private
|
214
|
+
|
215
|
+
def context
|
216
|
+
::Pwnlib::Context.context
|
217
|
+
end
|
212
218
|
end
|
213
219
|
|
214
220
|
# @!visibility private
|
215
221
|
def self.included(base)
|
216
|
-
|
217
|
-
base
|
222
|
+
base.include(IncludeContext)
|
223
|
+
class << base
|
224
|
+
include IncludeContext
|
225
|
+
end
|
218
226
|
end
|
219
227
|
end
|
220
228
|
end
|
data/lib/pwnlib/dynelf.rb
CHANGED
@@ -1,60 +1,126 @@
|
|
1
1
|
# encoding: ASCII-8BIT
|
2
2
|
|
3
|
+
require 'elftools'
|
4
|
+
|
3
5
|
require 'pwnlib/context'
|
4
6
|
require 'pwnlib/memleak'
|
5
7
|
require 'pwnlib/util/packing'
|
6
8
|
|
7
|
-
# TODO(hh): Use ELF datatype instead of magic offset
|
8
|
-
|
9
9
|
module Pwnlib
|
10
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.
|
11
|
+
# Given a function which can leak data at an arbitrary address, any symbol in any loaded library can be resolved.
|
13
12
|
class DynELF
|
14
|
-
|
15
|
-
DT_GNU_HASH = 0x6ffffef5
|
16
|
-
DT_HASH = 4
|
17
|
-
DT_STRTAB = 5
|
18
|
-
DT_SYMTAB = 6
|
19
|
-
|
20
|
-
attr_reader :libbase
|
13
|
+
attr_reader :libbase # @return [Integer] Base of lib.
|
21
14
|
|
15
|
+
# Instantiate a {Pwnlib::DynELF} object.
|
16
|
+
#
|
17
|
+
# @param [Integer] addr
|
18
|
+
# An address known to be inside the ELF.
|
19
|
+
#
|
20
|
+
# @yieldparam [Integer] leak_addr
|
21
|
+
# The start address that the leaker should leak from.
|
22
|
+
#
|
23
|
+
# @yieldreturn [String]
|
24
|
+
# A leaked non-empty byte string, starting from +leak_addr+.
|
22
25
|
def initialize(addr, &block)
|
23
26
|
@leak = ::Pwnlib::MemLeak.new(&block)
|
24
|
-
@libbase =
|
25
|
-
@elfclass = {
|
27
|
+
@libbase = find_base(addr)
|
28
|
+
@elfclass = { 0x1 => 32, 0x2 => 64 }[@leak.b(@libbase + 4)]
|
26
29
|
@elfword = @elfclass / 8
|
27
|
-
@unp = ->(x) { Util::Packing.public_send({ 32 => :u32, 64 => :u64 }[@elfclass], x) }
|
28
30
|
@dynamic = find_dynamic
|
29
31
|
@hshtab = @strtab = @symtab = nil
|
30
32
|
end
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
# Lookup a symbol from the ELF.
|
35
|
+
#
|
36
|
+
# @param [String] symbol
|
37
|
+
# The symbol name.
|
38
|
+
#
|
39
|
+
# @return [Integer, nil]
|
40
|
+
# The address of the symbol, or +nil+ if not found.
|
41
|
+
def lookup(symbol)
|
42
|
+
symbol = symbol.to_s
|
43
|
+
sym_size = { 32 => 16, 64 => 24 }[@elfclass]
|
44
|
+
# Leak GNU_HASH section header.
|
45
|
+
nbuckets = @leak.d(hshtab)
|
46
|
+
symndx = @leak.d(hshtab + 4)
|
47
|
+
maskwords = @leak.d(hshtab + 8)
|
48
|
+
|
49
|
+
l_gnu_buckets = hshtab + 16 + (@elfword * maskwords)
|
50
|
+
l_gnu_chain_zero = l_gnu_buckets + (4 * nbuckets) - (4 * symndx)
|
51
|
+
|
52
|
+
hsh = gnu_hash(symbol)
|
53
|
+
bucket = hsh % nbuckets
|
54
|
+
|
55
|
+
i = @leak.d(l_gnu_buckets + bucket * 4)
|
56
|
+
return nil if i.zero?
|
57
|
+
|
58
|
+
hsh2 = 0
|
59
|
+
while (hsh2 & 1).zero?
|
60
|
+
hsh2 = @leak.d(l_gnu_chain_zero + i * 4)
|
61
|
+
if ((hsh ^ hsh2) >> 1).zero?
|
62
|
+
sym = symtab + sym_size * i
|
63
|
+
st_name = @leak.d(sym)
|
64
|
+
name = @leak.n(strtab + st_name, symbol.length + 1)
|
65
|
+
if name == (symbol + "\x00")
|
66
|
+
offset = { 32 => 4, 64 => 8 }[@elfclass]
|
67
|
+
st_value = unpack(@leak.n(sym + offset, @elfword))
|
68
|
+
return @libbase + st_value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
i += 1
|
72
|
+
end
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
# Leak the BuildID of the remote libc.so.
|
77
|
+
#
|
78
|
+
# @return [String?]
|
79
|
+
# Return BuildID in hex format or +nil+.
|
80
|
+
def build_id
|
81
|
+
build_id_offsets.each do |offset|
|
82
|
+
next unless @leak.n(@libbase + offset + 12, 4) == "GNU\x00"
|
83
|
+
return @leak.n(@libbase + offset + 16, 20).unpack('H*').first
|
84
|
+
end
|
85
|
+
nil
|
37
86
|
end
|
38
87
|
|
39
88
|
private
|
40
89
|
|
90
|
+
PAGE_SIZE = 0x1000
|
91
|
+
PAGE_MASK = ~(PAGE_SIZE - 1)
|
92
|
+
|
93
|
+
def unpack(x)
|
94
|
+
Util::Packing.public_send({ 32 => :u32, 64 => :u64 }[@elfclass], x)
|
95
|
+
end
|
96
|
+
|
41
97
|
# Function used to generated GNU-style hashes for strings.
|
42
98
|
def gnu_hash(s)
|
43
99
|
s.bytes.reduce(5381) { |acc, elem| (acc * 33 + elem) & 0xffffffff }
|
44
100
|
end
|
45
101
|
|
102
|
+
# Get the base address of the ELF, based on heuristic of finding ELF header.
|
103
|
+
# A known address in ELF should be given.
|
104
|
+
def find_base(ptr)
|
105
|
+
ptr &= PAGE_MASK
|
106
|
+
loop do
|
107
|
+
return @base = ptr if @leak.n(ptr, 4) == "\x7fELF"
|
108
|
+
ptr -= PAGE_SIZE
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
46
112
|
def find_dynamic
|
47
113
|
e_phoff_offset = { 32 => 28, 64 => 32 }[@elfclass]
|
48
|
-
e_phoff = @libbase +
|
114
|
+
e_phoff = @libbase + unpack(@leak.n(@libbase + e_phoff_offset, @elfword))
|
49
115
|
phdr_size = { 32 => 32, 64 => 56 }[@elfclass]
|
50
116
|
loop do
|
51
117
|
ptype = @leak.d(e_phoff)
|
52
|
-
break if ptype == PT_DYNAMIC
|
118
|
+
break if ptype == ELFTools::Constants::PT::PT_DYNAMIC
|
53
119
|
e_phoff += phdr_size
|
54
120
|
end
|
55
121
|
offset = { 32 => 8, 64 => 16 }[@elfclass]
|
56
|
-
dyn =
|
57
|
-
# Sometimes this is an offset instead of an address
|
122
|
+
dyn = unpack(@leak.n(e_phoff + offset, @elfword))
|
123
|
+
# Sometimes this is an offset instead of an address.
|
58
124
|
dyn += @libbase if (0...0x400000).cover?(dyn)
|
59
125
|
dyn
|
60
126
|
end
|
@@ -64,8 +130,8 @@ module Pwnlib
|
|
64
130
|
ptr = @dynamic
|
65
131
|
loop do
|
66
132
|
tmp = @leak.n(ptr, @elfword * 2)
|
67
|
-
d_tag =
|
68
|
-
d_addr =
|
133
|
+
d_tag = unpack(tmp[0, @elfword])
|
134
|
+
d_addr = unpack(tmp[@elfword, @elfword])
|
69
135
|
break if d_tag.zero?
|
70
136
|
return d_addr if tag == d_tag
|
71
137
|
ptr += dyn_size
|
@@ -73,38 +139,35 @@ module Pwnlib
|
|
73
139
|
nil
|
74
140
|
end
|
75
141
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
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)
|
142
|
+
def hshtab
|
143
|
+
@hshtab ||= find_dt(ELFTools::Constants::DT::DT_GNU_HASH)
|
144
|
+
end
|
85
145
|
|
86
|
-
|
87
|
-
|
146
|
+
def strtab
|
147
|
+
@strtab ||= find_dt(ELFTools::Constants::DT::DT_STRTAB)
|
148
|
+
end
|
88
149
|
|
89
|
-
|
90
|
-
|
150
|
+
def symtab
|
151
|
+
@symtab ||= find_dt(ELFTools::Constants::DT::DT_SYMTAB)
|
152
|
+
end
|
91
153
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
nil
|
154
|
+
# Given the corpus of almost all libc to have been released on RedHat, Fedora, Ubuntu, Debian,
|
155
|
+
# etc. over the past several years, there is a strong possibility the GNU Build ID section will
|
156
|
+
# be at one of the specified addresses.
|
157
|
+
def build_id_offsets
|
158
|
+
{
|
159
|
+
i386: [0x174],
|
160
|
+
arm: [0x174],
|
161
|
+
thumb: [0x174],
|
162
|
+
aarch64: [0x238],
|
163
|
+
amd64: [0x270, 0x174],
|
164
|
+
powerpc: [0x174],
|
165
|
+
powerpc64: [0x238],
|
166
|
+
sparc: [0x174],
|
167
|
+
sparc64: [0x270]
|
168
|
+
}[context.arch.to_sym] || []
|
108
169
|
end
|
170
|
+
|
171
|
+
include Context
|
109
172
|
end
|
110
173
|
end
|
@@ -0,0 +1,267 @@
|
|
1
|
+
require 'elftools'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'rainbow'
|
4
|
+
|
5
|
+
require 'pwnlib/logger'
|
6
|
+
|
7
|
+
module Pwnlib
|
8
|
+
# ELF module includes classes for parsing an ELF file.
|
9
|
+
module ELF
|
10
|
+
# Main class for using {Pwnlib::ELF} module.
|
11
|
+
class ELF
|
12
|
+
# @return [OpenStruct] GOT symbols.
|
13
|
+
attr_reader :got
|
14
|
+
|
15
|
+
# @return [OpenStruct] PLT symbols.
|
16
|
+
attr_reader :plt
|
17
|
+
|
18
|
+
# @return [OpenStruct] All symbols.
|
19
|
+
attr_reader :symbols
|
20
|
+
|
21
|
+
# @return [Integer] Base address.
|
22
|
+
attr_reader :address
|
23
|
+
|
24
|
+
# Instantiate an {Pwnlib::ELF::ELF} object.
|
25
|
+
#
|
26
|
+
# Will show checksec information to stdout.
|
27
|
+
#
|
28
|
+
# @param [String] path
|
29
|
+
# The path to the ELF file.
|
30
|
+
# @param [Boolean] checksec
|
31
|
+
# The checksec information will be printed to stdout after ELF loaded. Pass +checksec: false+ to disable this
|
32
|
+
# feature.
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# ELF.new('/lib/x86_64-linux-gnu/libc.so.6')
|
36
|
+
# # RELRO: Partial RELRO
|
37
|
+
# # Stack: No canary found
|
38
|
+
# # NX: NX enabled
|
39
|
+
# # PIE: PIE enabled
|
40
|
+
# #=> #<Pwnlib::ELF::ELF:0x00559bd670dcb8>
|
41
|
+
def initialize(path, checksec: true)
|
42
|
+
path = File.realpath(path)
|
43
|
+
@elf_file = ELFTools::ELFFile.new(File.open(path, 'rb')) # rubocop:disable Style/AutoResourceCleanup
|
44
|
+
load_got
|
45
|
+
load_plt
|
46
|
+
load_symbols
|
47
|
+
@address = base_address
|
48
|
+
@load_addr = @address
|
49
|
+
show_info(path) if checksec
|
50
|
+
end
|
51
|
+
|
52
|
+
# Set the base address.
|
53
|
+
#
|
54
|
+
# Values in following tables will be changed simultaneously:
|
55
|
+
# got
|
56
|
+
# plt
|
57
|
+
# symbols
|
58
|
+
#
|
59
|
+
# @param [Integer] val
|
60
|
+
# Address to be changed to.
|
61
|
+
#
|
62
|
+
# @return [Integer]
|
63
|
+
# The new address.
|
64
|
+
def address=(val)
|
65
|
+
old = @address
|
66
|
+
@address = val
|
67
|
+
[@got, @plt, @symbols].compact.each do |tbl|
|
68
|
+
tbl.each_pair { |k, _| tbl[k] += val - old }
|
69
|
+
end
|
70
|
+
val
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return the protection information, wrapper with color codes.
|
74
|
+
#
|
75
|
+
# @return [String]
|
76
|
+
# The checksec information.
|
77
|
+
def checksec
|
78
|
+
[
|
79
|
+
'RELRO:'.ljust(10) + {
|
80
|
+
full: Rainbow('Full RELRO').green,
|
81
|
+
partial: Rainbow('Partial RELRO').yellow,
|
82
|
+
none: Rainbow('No RELRO').red
|
83
|
+
}[relro],
|
84
|
+
'Stack:'.ljust(10) + {
|
85
|
+
true => Rainbow('Canary found').green,
|
86
|
+
false => Rainbow('No canary found').red
|
87
|
+
}[canary?],
|
88
|
+
'NX:'.ljust(10) + {
|
89
|
+
true => Rainbow('NX enabled').green,
|
90
|
+
false => Rainbow('NX disabled').red
|
91
|
+
}[nx?],
|
92
|
+
'PIE:'.ljust(10) + {
|
93
|
+
true => Rainbow('PIE enabled').green,
|
94
|
+
false => Rainbow(format('No PIE (0x%x)', address)).red
|
95
|
+
}[pie?]
|
96
|
+
].join("\n")
|
97
|
+
end
|
98
|
+
|
99
|
+
# The method used in relro.
|
100
|
+
#
|
101
|
+
# @return [:full, :partial, :none]
|
102
|
+
def relro
|
103
|
+
return :none unless @elf_file.segment_by_type(:gnu_relro)
|
104
|
+
return :full if dynamic_tag(:bind_now)
|
105
|
+
flags = dynamic_tag(:flags)
|
106
|
+
return :full if flags && (flags.value & ::ELFTools::Constants::DF_BIND_NOW) != 0
|
107
|
+
flags1 = dynamic_tag(:flags_1)
|
108
|
+
return :full if flags1 && (flags1.value & ::ELFTools::Constants::DF_1_NOW) != 0
|
109
|
+
:partial
|
110
|
+
end
|
111
|
+
|
112
|
+
# Is this ELF file has canary?
|
113
|
+
#
|
114
|
+
# Actually judged by if +__stack_chk_fail+ in got symbols.
|
115
|
+
#
|
116
|
+
# @return [Boolean] Yes or not.
|
117
|
+
def canary?
|
118
|
+
@got.respond_to?('__stack_chk_fail')
|
119
|
+
end
|
120
|
+
|
121
|
+
# Is stack executable?
|
122
|
+
#
|
123
|
+
# @return [Boolean] Yes or not.
|
124
|
+
def nx?
|
125
|
+
!@elf_file.segment_by_type(:gnu_stack).executable?
|
126
|
+
end
|
127
|
+
|
128
|
+
# Is this ELF file a position-independent executable?
|
129
|
+
#
|
130
|
+
# @return [Boolean] Yes or not.
|
131
|
+
def pie?
|
132
|
+
@elf_file.elf_type == 'DYN'
|
133
|
+
end
|
134
|
+
|
135
|
+
# There's too many objects inside, let pry not so verbose.
|
136
|
+
# @return [nil]
|
137
|
+
def inspect
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
|
141
|
+
# Yields the ELF's virtual address space for the specified string or regexp.
|
142
|
+
# Returns an Enumerator if no block given.
|
143
|
+
#
|
144
|
+
# @param [String, Regexp] needle
|
145
|
+
# The specified string to search.
|
146
|
+
#
|
147
|
+
# @return [Enumerator<Integer>]
|
148
|
+
# An enumerator for offsets in ELF's virtual address space.
|
149
|
+
#
|
150
|
+
# @example
|
151
|
+
# ELF.new('/bin/sh', checksec: false).find('ELF')
|
152
|
+
# #=> #<Enumerator: ...>
|
153
|
+
#
|
154
|
+
# ELF.new('/bin/sh', checksec: false).find(/E.F/).each { |i| puts i.hex }
|
155
|
+
# # 0x1
|
156
|
+
# # 0x11477
|
157
|
+
# # 0x1c84f
|
158
|
+
# # 0x1d5ee
|
159
|
+
# #=> true
|
160
|
+
def search(needle)
|
161
|
+
return enum_for(:search, needle) unless block_given?
|
162
|
+
load_address_fixup = @address - @load_addr
|
163
|
+
stream = @elf_file.stream
|
164
|
+
@elf_file.each_segments do |seg|
|
165
|
+
addr = seg.header.p_vaddr
|
166
|
+
memsz = seg.header.p_memsz
|
167
|
+
offset = seg.header.p_offset
|
168
|
+
|
169
|
+
stream.pos = offset
|
170
|
+
data = stream.read(memsz).ljust(seg.header.p_filesz, "\x00")
|
171
|
+
|
172
|
+
offset = 0
|
173
|
+
loop do
|
174
|
+
offset = data.index(needle, offset)
|
175
|
+
break if offset.nil?
|
176
|
+
yield (addr + offset + load_address_fixup)
|
177
|
+
offset += 1
|
178
|
+
end
|
179
|
+
end
|
180
|
+
true
|
181
|
+
end
|
182
|
+
alias find search
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def show_info(path)
|
187
|
+
log.info(path.inspect)
|
188
|
+
log.indented(checksec, level: ::Pwnlib::Logger::INFO)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Get the dynamic tag with +type+.
|
192
|
+
# @return [ELFTools::Dynamic::Tag, nil]
|
193
|
+
def dynamic_tag(type)
|
194
|
+
dynamic = @elf_file.segment_by_type(:dynamic) || @elf.section_by_name('.dynamic')
|
195
|
+
return nil if dynamic.nil? # No dynamic present, might be static-linked.
|
196
|
+
dynamic.tag_by_type(type)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Load got symbols
|
200
|
+
def load_got
|
201
|
+
@got = OpenStruct.new
|
202
|
+
sections_by_types(%i(rel rela)).each do |rel_sec|
|
203
|
+
symtab = @elf_file.section_at(rel_sec.header.sh_link)
|
204
|
+
next unless symtab.respond_to?(:symbol_at)
|
205
|
+
rel_sec.relocations.each do |rel|
|
206
|
+
symbol = symtab.symbol_at(rel.symbol_index)
|
207
|
+
next if symbol.nil? # Unusual case.
|
208
|
+
@got[symbol.name] = rel.header.r_offset.to_i
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
PLT_OFFSET = 0x10 # magic offset, correct in i386/amd64.
|
214
|
+
# Load all plt symbols.
|
215
|
+
def load_plt
|
216
|
+
# Unlike pwntools-python, which use unicorn emulating instructions to find plt(s).
|
217
|
+
# Here only use section information, which won't find any plt(s) when compile option '-Wl' is enabled.
|
218
|
+
#
|
219
|
+
# The implementation here same as pwntools-python 3.5, and supports i386 and amd64 only.
|
220
|
+
@plt = nil
|
221
|
+
plt_sec = @elf_file.section_by_name('.plt')
|
222
|
+
return log.warn('No PLT section found, PLT not loaded') if plt_sec.nil?
|
223
|
+
rel_sec = @elf_file.section_by_name('.rel.plt') || @elf_file.section_by_name('.rela.plt')
|
224
|
+
return log.warn('No REL.PLT section found, PLT not loaded') if rel_sec.nil?
|
225
|
+
symtab = @elf_file.section_at(rel_sec.header.sh_link)
|
226
|
+
return unless symtab.respond_to?(:symbol_at) # unusual case
|
227
|
+
@plt = OpenStruct.new
|
228
|
+
address = plt_sec.header.sh_addr.to_i + PLT_OFFSET
|
229
|
+
rel_sec.relocations.each do |rel|
|
230
|
+
symbol = symtab.symbol_at(rel.symbol_index)
|
231
|
+
next if symbol.nil? # unusual case
|
232
|
+
@plt[symbol.name] = address
|
233
|
+
address += PLT_OFFSET
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Load all exist symbols.
|
238
|
+
def load_symbols
|
239
|
+
@symbols = OpenStruct.new
|
240
|
+
@elf_file.each_sections do |section|
|
241
|
+
next unless section.respond_to?(:symbols)
|
242
|
+
section.each_symbols do |symbol|
|
243
|
+
# Don't care symbols without name.
|
244
|
+
next if symbol.name.empty?
|
245
|
+
next if symbol.header.st_value.zero?
|
246
|
+
# TODO(david942j): handle symbols with same name.
|
247
|
+
@symbols[symbol.name] = symbol.header.st_value.to_i
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def sections_by_types(types)
|
253
|
+
types.map { |type| @elf_file.sections_by_type(type) }.flatten
|
254
|
+
end
|
255
|
+
|
256
|
+
def base_address
|
257
|
+
return 0 if pie?
|
258
|
+
# Find the min of PT_LOAD's p_vaddr
|
259
|
+
@elf_file.segments_by_type(:load)
|
260
|
+
.map { |seg| seg.header.p_vaddr }
|
261
|
+
.min
|
262
|
+
end
|
263
|
+
|
264
|
+
include ::Pwnlib::Logger
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|