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.
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