pwntools 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -11
  3. data/Rakefile +5 -1
  4. data/lib/pwn.rb +9 -7
  5. data/lib/pwnlib/abi.rb +60 -0
  6. data/lib/pwnlib/asm.rb +146 -0
  7. data/lib/pwnlib/constants/constant.rb +16 -2
  8. data/lib/pwnlib/constants/constants.rb +35 -19
  9. data/lib/pwnlib/constants/linux/amd64.rb +30 -1
  10. data/lib/pwnlib/context.rb +25 -17
  11. data/lib/pwnlib/dynelf.rb +117 -54
  12. data/lib/pwnlib/elf/elf.rb +267 -0
  13. data/lib/pwnlib/ext/helper.rb +4 -4
  14. data/lib/pwnlib/logger.rb +87 -0
  15. data/lib/pwnlib/memleak.rb +58 -29
  16. data/lib/pwnlib/pwn.rb +19 -8
  17. data/lib/pwnlib/reg_sort.rb +102 -108
  18. data/lib/pwnlib/shellcraft/generators/amd64/common/common.rb +14 -0
  19. data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +17 -0
  20. data/lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb +31 -0
  21. data/lib/pwnlib/shellcraft/generators/amd64/common/mov.rb +127 -0
  22. data/lib/pwnlib/shellcraft/generators/amd64/common/nop.rb +16 -0
  23. data/lib/pwnlib/shellcraft/generators/amd64/common/popad.rb +27 -0
  24. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb +64 -0
  25. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +19 -0
  26. data/lib/pwnlib/shellcraft/generators/amd64/common/ret.rb +32 -0
  27. data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +19 -0
  28. data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +21 -0
  29. data/lib/pwnlib/shellcraft/generators/amd64/linux/linux.rb +14 -0
  30. data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +19 -0
  31. data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +19 -0
  32. data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +21 -0
  33. data/lib/pwnlib/shellcraft/generators/helper.rb +106 -0
  34. data/lib/pwnlib/shellcraft/generators/i386/common/common.rb +14 -0
  35. data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +17 -0
  36. data/lib/pwnlib/shellcraft/generators/i386/common/mov.rb +90 -0
  37. data/lib/pwnlib/shellcraft/generators/i386/common/nop.rb +16 -0
  38. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr.rb +39 -0
  39. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +19 -0
  40. data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +19 -0
  41. data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +19 -0
  42. data/lib/pwnlib/shellcraft/generators/i386/linux/linux.rb +14 -0
  43. data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +19 -0
  44. data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +19 -0
  45. data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +19 -0
  46. data/lib/pwnlib/shellcraft/generators/x86/common/common.rb +26 -0
  47. data/lib/pwnlib/shellcraft/generators/x86/common/infloop.rb +22 -0
  48. data/lib/pwnlib/shellcraft/generators/x86/common/mov.rb +15 -0
  49. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr.rb +15 -0
  50. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr_array.rb +85 -0
  51. data/lib/pwnlib/shellcraft/generators/x86/common/setregs.rb +82 -0
  52. data/lib/pwnlib/shellcraft/generators/x86/linux/execve.rb +69 -0
  53. data/lib/pwnlib/shellcraft/generators/x86/linux/linux.rb +14 -0
  54. data/lib/pwnlib/shellcraft/generators/x86/linux/ls.rb +66 -0
  55. data/lib/pwnlib/shellcraft/generators/x86/linux/sh.rb +52 -0
  56. data/lib/pwnlib/shellcraft/generators/x86/linux/syscall.rb +52 -0
  57. data/lib/pwnlib/shellcraft/registers.rb +145 -0
  58. data/lib/pwnlib/shellcraft/shellcraft.rb +67 -0
  59. data/lib/pwnlib/timer.rb +60 -0
  60. data/lib/pwnlib/tubes/buffer.rb +96 -0
  61. data/lib/pwnlib/tubes/sock.rb +95 -0
  62. data/lib/pwnlib/tubes/tube.rb +270 -0
  63. data/lib/pwnlib/util/cyclic.rb +95 -94
  64. data/lib/pwnlib/util/fiddling.rb +256 -220
  65. data/lib/pwnlib/util/getdents.rb +83 -0
  66. data/lib/pwnlib/util/hexdump.rb +109 -108
  67. data/lib/pwnlib/util/lists.rb +55 -0
  68. data/lib/pwnlib/util/packing.rb +226 -228
  69. data/lib/pwnlib/util/ruby.rb +18 -0
  70. data/lib/pwnlib/version.rb +2 -1
  71. data/test/abi_test.rb +21 -0
  72. data/test/asm_test.rb +104 -0
  73. data/test/constants/constant_test.rb +1 -0
  74. data/test/constants/constants_test.rb +4 -2
  75. data/test/context_test.rb +1 -0
  76. data/test/data/echo.rb +20 -0
  77. data/test/data/elfs/Makefile +22 -0
  78. data/test/data/elfs/amd64.frelro.elf +0 -0
  79. data/test/data/elfs/amd64.frelro.pie.elf +0 -0
  80. data/test/data/elfs/amd64.nrelro.elf +0 -0
  81. data/test/data/elfs/amd64.prelro.elf +0 -0
  82. data/test/data/elfs/i386.frelro.pie.elf +0 -0
  83. data/test/data/elfs/i386.prelro.elf +0 -0
  84. data/test/data/elfs/source.cpp +19 -0
  85. data/test/data/flag +1 -0
  86. data/test/data/lib32/ld.so.2 +0 -0
  87. data/test/data/lib32/libc.so.6 +0 -0
  88. data/test/data/lib64/ld.so.2 +0 -0
  89. data/test/data/lib64/libc.so.6 +0 -0
  90. data/test/dynelf_test.rb +59 -24
  91. data/test/elf/elf_test.rb +120 -0
  92. data/test/ext_test.rb +3 -2
  93. data/test/files/use_pwnlib.rb +1 -1
  94. data/test/logger_test.rb +61 -0
  95. data/test/memleak_test.rb +4 -33
  96. data/test/reg_sort_test.rb +3 -1
  97. data/test/shellcraft/infloop_test.rb +26 -0
  98. data/test/shellcraft/linux/ls_test.rb +108 -0
  99. data/test/shellcraft/linux/sh_test.rb +119 -0
  100. data/test/shellcraft/linux/syscalls/execve_test.rb +136 -0
  101. data/test/shellcraft/linux/syscalls/syscall_test.rb +83 -0
  102. data/test/shellcraft/memcpy_test.rb +35 -0
  103. data/test/shellcraft/mov_test.rb +98 -0
  104. data/test/shellcraft/nop_test.rb +26 -0
  105. data/test/shellcraft/popad_test.rb +29 -0
  106. data/test/shellcraft/pushstr_array_test.rb +91 -0
  107. data/test/shellcraft/pushstr_test.rb +108 -0
  108. data/test/shellcraft/registers_test.rb +32 -0
  109. data/test/shellcraft/ret_test.rb +30 -0
  110. data/test/shellcraft/setregs_test.rb +62 -0
  111. data/test/shellcraft/shellcraft_test.rb +28 -0
  112. data/test/test_helper.rb +12 -1
  113. data/test/timer_test.rb +23 -0
  114. data/test/tubes/buffer_test.rb +45 -0
  115. data/test/tubes/sock_test.rb +68 -0
  116. data/test/tubes/tube_test.rb +241 -0
  117. data/test/util/cyclic_test.rb +2 -1
  118. data/test/util/fiddling_test.rb +2 -1
  119. data/test/util/getdents_test.rb +32 -0
  120. data/test/util/hexdump_test.rb +7 -9
  121. data/test/util/lists_test.rb +21 -0
  122. data/test/util/packing_test.rb +4 -3
  123. 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
@@ -17,7 +17,7 @@ module Pwnlib
17
17
  newline: "\n",
18
18
  os: 'linux',
19
19
  signed: false,
20
- timeout: Float::INFINITY
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
- # 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)?
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): improve performance for this if this is too slow, since we use this in many
109
- # places that has argument endian / signed / ...
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
- # Difference from Python pwntools:
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
- # For include.
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
- def context
211
- ::Pwnlib::Context.context
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
- # XXX(Darkpi): Should we do this?
217
- base.__send__(:private, :context)
222
+ base.include(IncludeContext)
223
+ class << base
224
+ include IncludeContext
225
+ end
218
226
  end
219
227
  end
220
228
  end
@@ -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
- 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
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 = @leak.find_elf_base(addr)
25
- @elfclass = { "\x01" => 32, "\x02" => 64 }[@leak.b(@libbase + 4)]
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
- 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)
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 + @unp.call(@leak.n(@libbase + e_phoff_offset, @elfword))
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 = @unp.call(@leak.n(e_phoff + offset, @elfword))
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 = @unp.call(tmp[0, @elfword])
68
- d_addr = @unp.call(tmp[@elfword, @elfword])
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 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)
142
+ def hshtab
143
+ @hshtab ||= find_dt(ELFTools::Constants::DT::DT_GNU_HASH)
144
+ end
85
145
 
86
- hsh = gnu_hash(symb)
87
- bucket = hsh % nbuckets
146
+ def strtab
147
+ @strtab ||= find_dt(ELFTools::Constants::DT::DT_STRTAB)
148
+ end
88
149
 
89
- i = @leak.d(l_gnu_buckets + bucket * 4)
90
- return nil if i.zero?
150
+ def symtab
151
+ @symtab ||= find_dt(ELFTools::Constants::DT::DT_SYMTAB)
152
+ end
91
153
 
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
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