pwntools 0.1.0 → 1.0.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 +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
|