pwntools 0.1.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +96 -15
  3. data/Rakefile +8 -2
  4. data/lib/pwn.rb +10 -7
  5. data/lib/pwnlib/abi.rb +61 -0
  6. data/lib/pwnlib/asm.rb +357 -0
  7. data/lib/pwnlib/constants/constant.rb +19 -3
  8. data/lib/pwnlib/constants/constants.rb +46 -20
  9. data/lib/pwnlib/constants/linux/amd64.rb +32 -1
  10. data/lib/pwnlib/constants/linux/i386.rb +2 -0
  11. data/lib/pwnlib/context.rb +128 -27
  12. data/lib/pwnlib/dynelf.rb +122 -54
  13. data/lib/pwnlib/elf/elf.rb +340 -0
  14. data/lib/pwnlib/errors.rb +31 -0
  15. data/lib/pwnlib/ext/array.rb +2 -1
  16. data/lib/pwnlib/ext/helper.rb +6 -5
  17. data/lib/pwnlib/ext/integer.rb +2 -1
  18. data/lib/pwnlib/ext/string.rb +3 -2
  19. data/lib/pwnlib/logger.rb +245 -0
  20. data/lib/pwnlib/memleak.rb +59 -29
  21. data/lib/pwnlib/pwn.rb +27 -9
  22. data/lib/pwnlib/reg_sort.rb +109 -110
  23. data/lib/pwnlib/runner.rb +53 -0
  24. data/lib/pwnlib/shellcraft/generators/amd64/common/common.rb +16 -0
  25. data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +24 -0
  26. data/lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb +35 -0
  27. data/lib/pwnlib/shellcraft/generators/amd64/common/mov.rb +131 -0
  28. data/lib/pwnlib/shellcraft/generators/amd64/common/nop.rb +18 -0
  29. data/lib/pwnlib/shellcraft/generators/amd64/common/popad.rb +28 -0
  30. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb +66 -0
  31. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +24 -0
  32. data/lib/pwnlib/shellcraft/generators/amd64/common/ret.rb +33 -0
  33. data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +24 -0
  34. data/lib/pwnlib/shellcraft/generators/amd64/linux/cat.rb +24 -0
  35. data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +24 -0
  36. data/lib/pwnlib/shellcraft/generators/amd64/linux/exit.rb +24 -0
  37. data/lib/pwnlib/shellcraft/generators/amd64/linux/linux.rb +16 -0
  38. data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +24 -0
  39. data/lib/pwnlib/shellcraft/generators/amd64/linux/open.rb +24 -0
  40. data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +24 -0
  41. data/lib/pwnlib/shellcraft/generators/amd64/linux/sleep.rb +24 -0
  42. data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +24 -0
  43. data/lib/pwnlib/shellcraft/generators/helper.rb +115 -0
  44. data/lib/pwnlib/shellcraft/generators/i386/common/common.rb +16 -0
  45. data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +24 -0
  46. data/lib/pwnlib/shellcraft/generators/i386/common/memcpy.rb +34 -0
  47. data/lib/pwnlib/shellcraft/generators/i386/common/mov.rb +93 -0
  48. data/lib/pwnlib/shellcraft/generators/i386/common/nop.rb +18 -0
  49. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr.rb +41 -0
  50. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +24 -0
  51. data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +24 -0
  52. data/lib/pwnlib/shellcraft/generators/i386/linux/cat.rb +24 -0
  53. data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +24 -0
  54. data/lib/pwnlib/shellcraft/generators/i386/linux/exit.rb +24 -0
  55. data/lib/pwnlib/shellcraft/generators/i386/linux/linux.rb +16 -0
  56. data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +24 -0
  57. data/lib/pwnlib/shellcraft/generators/i386/linux/open.rb +24 -0
  58. data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +24 -0
  59. data/lib/pwnlib/shellcraft/generators/i386/linux/sleep.rb +24 -0
  60. data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +24 -0
  61. data/lib/pwnlib/shellcraft/generators/x86/common/common.rb +29 -0
  62. data/lib/pwnlib/shellcraft/generators/x86/common/infloop.rb +24 -0
  63. data/lib/pwnlib/shellcraft/generators/x86/common/memcpy.rb +17 -0
  64. data/lib/pwnlib/shellcraft/generators/x86/common/mov.rb +17 -0
  65. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr.rb +17 -0
  66. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr_array.rb +86 -0
  67. data/lib/pwnlib/shellcraft/generators/x86/common/setregs.rb +84 -0
  68. data/lib/pwnlib/shellcraft/generators/x86/linux/cat.rb +54 -0
  69. data/lib/pwnlib/shellcraft/generators/x86/linux/execve.rb +72 -0
  70. data/lib/pwnlib/shellcraft/generators/x86/linux/exit.rb +34 -0
  71. data/lib/pwnlib/shellcraft/generators/x86/linux/linux.rb +16 -0
  72. data/lib/pwnlib/shellcraft/generators/x86/linux/ls.rb +67 -0
  73. data/lib/pwnlib/shellcraft/generators/x86/linux/open.rb +47 -0
  74. data/lib/pwnlib/shellcraft/generators/x86/linux/sh.rb +53 -0
  75. data/lib/pwnlib/shellcraft/generators/x86/linux/sleep.rb +52 -0
  76. data/lib/pwnlib/shellcraft/generators/x86/linux/syscall.rb +52 -0
  77. data/lib/pwnlib/shellcraft/registers.rb +148 -0
  78. data/lib/pwnlib/shellcraft/shellcraft.rb +73 -0
  79. data/lib/pwnlib/timer.rb +67 -0
  80. data/lib/pwnlib/tubes/buffer.rb +99 -0
  81. data/lib/pwnlib/tubes/process.rb +155 -0
  82. data/lib/pwnlib/tubes/serialtube.rb +114 -0
  83. data/lib/pwnlib/tubes/sock.rb +101 -0
  84. data/lib/pwnlib/tubes/tube.rb +442 -0
  85. data/lib/pwnlib/ui.rb +21 -0
  86. data/lib/pwnlib/util/cyclic.rb +97 -94
  87. data/lib/pwnlib/util/fiddling.rb +288 -220
  88. data/lib/pwnlib/util/getdents.rb +85 -0
  89. data/lib/pwnlib/util/hexdump.rb +116 -112
  90. data/lib/pwnlib/util/lists.rb +58 -0
  91. data/lib/pwnlib/util/packing.rb +223 -228
  92. data/lib/pwnlib/util/ruby.rb +19 -0
  93. data/lib/pwnlib/version.rb +3 -1
  94. data/test/abi_test.rb +22 -0
  95. data/test/asm_test.rb +177 -0
  96. data/test/constants/constant_test.rb +2 -0
  97. data/test/constants/constants_test.rb +5 -2
  98. data/test/context_test.rb +14 -3
  99. data/test/data/assembly/aarch64.s +19 -0
  100. data/test/data/assembly/amd64.s +21 -0
  101. data/test/data/assembly/arm.s +9 -0
  102. data/test/data/assembly/i386.s +21 -0
  103. data/test/data/assembly/mips.s +16 -0
  104. data/test/data/assembly/mips64.s +6 -0
  105. data/test/data/assembly/powerpc.s +18 -0
  106. data/test/data/assembly/powerpc64.s +36 -0
  107. data/test/data/assembly/sparc.s +33 -0
  108. data/test/data/assembly/sparc64.s +5 -0
  109. data/test/data/assembly/thumb.s +37 -0
  110. data/test/data/echo.rb +16 -0
  111. data/test/data/elfs/Makefile +24 -0
  112. data/test/data/elfs/amd64.frelro.elf +0 -0
  113. data/test/data/elfs/amd64.frelro.pie.elf +0 -0
  114. data/test/data/elfs/amd64.nrelro.elf +0 -0
  115. data/test/data/elfs/amd64.prelro.elf +0 -0
  116. data/test/data/elfs/amd64.static.elf +0 -0
  117. data/test/data/elfs/i386.frelro.pie.elf +0 -0
  118. data/test/data/elfs/i386.prelro.elf +0 -0
  119. data/test/data/elfs/source.cpp +19 -0
  120. data/test/data/flag +1 -0
  121. data/test/data/lib32/ld.so.2 +0 -0
  122. data/test/data/lib32/libc.so.6 +0 -0
  123. data/test/data/lib64/ld.so.2 +0 -0
  124. data/test/data/lib64/libc.so.6 +0 -0
  125. data/test/dynelf_test.rb +62 -25
  126. data/test/elf/elf_test.rb +147 -0
  127. data/test/ext_test.rb +4 -2
  128. data/test/files/use_pwn.rb +3 -6
  129. data/test/files/use_pwnlib.rb +2 -1
  130. data/test/full_file_test.rb +6 -0
  131. data/test/logger_test.rb +120 -0
  132. data/test/memleak_test.rb +5 -33
  133. data/test/reg_sort_test.rb +4 -1
  134. data/test/runner_test.rb +32 -0
  135. data/test/shellcraft/infloop_test.rb +27 -0
  136. data/test/shellcraft/linux/cat_test.rb +87 -0
  137. data/test/shellcraft/linux/ls_test.rb +109 -0
  138. data/test/shellcraft/linux/sh_test.rb +120 -0
  139. data/test/shellcraft/linux/sleep_test.rb +68 -0
  140. data/test/shellcraft/linux/syscalls/execve_test.rb +137 -0
  141. data/test/shellcraft/linux/syscalls/exit_test.rb +57 -0
  142. data/test/shellcraft/linux/syscalls/open_test.rb +87 -0
  143. data/test/shellcraft/linux/syscalls/syscall_test.rb +84 -0
  144. data/test/shellcraft/memcpy_test.rb +50 -0
  145. data/test/shellcraft/mov_test.rb +99 -0
  146. data/test/shellcraft/nop_test.rb +27 -0
  147. data/test/shellcraft/popad_test.rb +30 -0
  148. data/test/shellcraft/pushstr_array_test.rb +92 -0
  149. data/test/shellcraft/pushstr_test.rb +109 -0
  150. data/test/shellcraft/registers_test.rb +33 -0
  151. data/test/shellcraft/ret_test.rb +31 -0
  152. data/test/shellcraft/setregs_test.rb +63 -0
  153. data/test/shellcraft/shellcraft_test.rb +30 -0
  154. data/test/test_helper.rb +61 -2
  155. data/test/timer_test.rb +42 -0
  156. data/test/tubes/buffer_test.rb +46 -0
  157. data/test/tubes/process_test.rb +105 -0
  158. data/test/tubes/serialtube_test.rb +162 -0
  159. data/test/tubes/sock_test.rb +68 -0
  160. data/test/tubes/tube_test.rb +320 -0
  161. data/test/ui_test.rb +18 -0
  162. data/test/util/cyclic_test.rb +3 -1
  163. data/test/util/fiddling_test.rb +12 -3
  164. data/test/util/getdents_test.rb +33 -0
  165. data/test/util/hexdump_test.rb +9 -10
  166. data/test/util/lists_test.rb +22 -0
  167. data/test/util/packing_test.rb +5 -3
  168. metadata +357 -37
data/lib/pwnlib/dynelf.rb CHANGED
@@ -1,60 +1,130 @@
1
1
  # encoding: ASCII-8BIT
2
+ # frozen_string_literal: true
3
+
4
+ require 'elftools'
2
5
 
3
6
  require 'pwnlib/context'
4
7
  require 'pwnlib/memleak'
5
8
  require 'pwnlib/util/packing'
6
9
 
7
- # TODO(hh): Use ELF datatype instead of magic offset
8
-
9
10
  module Pwnlib
10
11
  # 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.
12
+ # Given a function which can leak data at an arbitrary address, any symbol in any loaded library can be resolved.
13
13
  class DynELF
14
- PT_DYNAMIC = 2
15
- DT_GNU_HASH = 0x6ffffef5
16
- DT_HASH = 4
17
- DT_STRTAB = 5
18
- DT_SYMTAB = 6
19
-
20
- attr_reader :libbase
14
+ attr_reader :libbase # @return [Integer] Base of lib.
21
15
 
16
+ # Instantiate a {Pwnlib::DynELF} object.
17
+ #
18
+ # @param [Integer] addr
19
+ # An address known to be inside the ELF.
20
+ #
21
+ # @yieldparam [Integer] leak_addr
22
+ # The start address that the leaker should leak from.
23
+ #
24
+ # @yieldreturn [String]
25
+ # A leaked non-empty byte string, starting from +leak_addr+.
22
26
  def initialize(addr, &block)
23
27
  @leak = ::Pwnlib::MemLeak.new(&block)
24
- @libbase = @leak.find_elf_base(addr)
25
- @elfclass = { "\x01" => 32, "\x02" => 64 }[@leak.b(@libbase + 4)]
28
+ @libbase = find_base(addr)
29
+ @elfclass = { 0x1 => 32, 0x2 => 64 }[@leak.b(@libbase + 4)]
26
30
  @elfword = @elfclass / 8
27
- @unp = ->(x) { Util::Packing.public_send({ 32 => :u32, 64 => :u64 }[@elfclass], x) }
28
31
  @dynamic = find_dynamic
29
32
  @hshtab = @strtab = @symtab = nil
30
33
  end
31
34
 
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)
35
+ # Lookup a symbol from the ELF.
36
+ #
37
+ # @param [String] symbol
38
+ # The symbol name.
39
+ #
40
+ # @return [Integer, nil]
41
+ # The address of the symbol, or +nil+ if not found.
42
+ def lookup(symbol)
43
+ symbol = symbol.to_s
44
+ sym_size = { 32 => 16, 64 => 24 }[@elfclass]
45
+ # Leak GNU_HASH section header.
46
+ nbuckets = @leak.d(hshtab)
47
+ symndx = @leak.d(hshtab + 4)
48
+ maskwords = @leak.d(hshtab + 8)
49
+
50
+ l_gnu_buckets = hshtab + 16 + (@elfword * maskwords)
51
+ l_gnu_chain_zero = l_gnu_buckets + (4 * nbuckets) - (4 * symndx)
52
+
53
+ hsh = gnu_hash(symbol)
54
+ bucket = hsh % nbuckets
55
+
56
+ i = @leak.d(l_gnu_buckets + bucket * 4)
57
+ return nil if i.zero?
58
+
59
+ hsh2 = 0
60
+ while (hsh2 & 1).zero?
61
+ hsh2 = @leak.d(l_gnu_chain_zero + i * 4)
62
+ if ((hsh ^ hsh2) >> 1).zero?
63
+ sym = symtab + sym_size * i
64
+ st_name = @leak.d(sym)
65
+ name = @leak.n(strtab + st_name, symbol.length + 1)
66
+ if name == ("#{symbol}\x00")
67
+ offset = { 32 => 4, 64 => 8 }[@elfclass]
68
+ st_value = unpack(@leak.n(sym + offset, @elfword))
69
+ return @libbase + st_value
70
+ end
71
+ end
72
+ i += 1
73
+ end
74
+ nil
75
+ end
76
+
77
+ # Leak the BuildID of the remote libc.so.
78
+ #
79
+ # @return [String?]
80
+ # Return BuildID in hex format or +nil+.
81
+ def build_id
82
+ build_id_offsets.each do |offset|
83
+ next unless @leak.n(@libbase + offset + 12, 4) == "GNU\x00"
84
+
85
+ return @leak.n(@libbase + offset + 16, 20).unpack1('H*')
86
+ end
87
+ nil
37
88
  end
38
89
 
39
90
  private
40
91
 
92
+ PAGE_SIZE = 0x1000
93
+ PAGE_MASK = ~(PAGE_SIZE - 1)
94
+
95
+ def unpack(x)
96
+ Util::Packing.public_send({ 32 => :u32, 64 => :u64 }[@elfclass], x)
97
+ end
98
+
41
99
  # Function used to generated GNU-style hashes for strings.
42
100
  def gnu_hash(s)
43
101
  s.bytes.reduce(5381) { |acc, elem| (acc * 33 + elem) & 0xffffffff }
44
102
  end
45
103
 
104
+ # Get the base address of the ELF, based on heuristic of finding ELF header.
105
+ # A known address in ELF should be given.
106
+ def find_base(ptr)
107
+ ptr &= PAGE_MASK
108
+ loop do
109
+ return @base = ptr if @leak.n(ptr, 4) == "\x7fELF"
110
+
111
+ ptr -= PAGE_SIZE
112
+ end
113
+ end
114
+
46
115
  def find_dynamic
47
116
  e_phoff_offset = { 32 => 28, 64 => 32 }[@elfclass]
48
- e_phoff = @libbase + @unp.call(@leak.n(@libbase + e_phoff_offset, @elfword))
117
+ e_phoff = @libbase + unpack(@leak.n(@libbase + e_phoff_offset, @elfword))
49
118
  phdr_size = { 32 => 32, 64 => 56 }[@elfclass]
50
119
  loop do
51
120
  ptype = @leak.d(e_phoff)
52
- break if ptype == PT_DYNAMIC
121
+ break if ptype == ELFTools::Constants::PT::PT_DYNAMIC
122
+
53
123
  e_phoff += phdr_size
54
124
  end
55
125
  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
126
+ dyn = unpack(@leak.n(e_phoff + offset, @elfword))
127
+ # Sometimes this is an offset instead of an address.
58
128
  dyn += @libbase if (0...0x400000).cover?(dyn)
59
129
  dyn
60
130
  end
@@ -64,47 +134,45 @@ module Pwnlib
64
134
  ptr = @dynamic
65
135
  loop do
66
136
  tmp = @leak.n(ptr, @elfword * 2)
67
- d_tag = @unp.call(tmp[0, @elfword])
68
- d_addr = @unp.call(tmp[@elfword, @elfword])
137
+ d_tag = unpack(tmp[0, @elfword])
138
+ d_addr = unpack(tmp[@elfword, @elfword])
69
139
  break if d_tag.zero?
70
140
  return d_addr if tag == d_tag
141
+
71
142
  ptr += dyn_size
72
143
  end
73
144
  nil
74
145
  end
75
146
 
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)
147
+ def hshtab
148
+ @hshtab ||= find_dt(ELFTools::Constants::DT::DT_GNU_HASH)
149
+ end
85
150
 
86
- hsh = gnu_hash(symb)
87
- bucket = hsh % nbuckets
151
+ def strtab
152
+ @strtab ||= find_dt(ELFTools::Constants::DT::DT_STRTAB)
153
+ end
88
154
 
89
- i = @leak.d(l_gnu_buckets + bucket * 4)
90
- return nil if i.zero?
155
+ def symtab
156
+ @symtab ||= find_dt(ELFTools::Constants::DT::DT_SYMTAB)
157
+ end
91
158
 
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
159
+ # Given the corpus of almost all libc to have been released on RedHat, Fedora, Ubuntu, Debian,
160
+ # etc. over the past several years, there is a strong possibility the GNU Build ID section will
161
+ # be at one of the specified addresses.
162
+ def build_id_offsets
163
+ {
164
+ i386: [0x174],
165
+ arm: [0x174],
166
+ thumb: [0x174],
167
+ aarch64: [0x238],
168
+ amd64: [0x270, 0x174],
169
+ powerpc: [0x174],
170
+ powerpc64: [0x238],
171
+ sparc: [0x174],
172
+ sparc64: [0x270]
173
+ }[context.arch.to_sym] || []
108
174
  end
175
+
176
+ include Context
109
177
  end
110
178
  end
@@ -0,0 +1,340 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+
5
+ require 'elftools'
6
+ require 'one_gadget/one_gadget'
7
+ require 'rainbow'
8
+
9
+ require 'pwnlib/logger'
10
+
11
+ module Pwnlib
12
+ # ELF module includes classes for parsing an ELF file.
13
+ module ELF
14
+ # Main class for using {Pwnlib::ELF} module.
15
+ class ELF
16
+ # @return [OpenStruct] GOT symbols.
17
+ attr_reader :got
18
+
19
+ # @return [OpenStruct] PLT symbols.
20
+ attr_reader :plt
21
+
22
+ # @return [OpenStruct] All symbols.
23
+ attr_reader :symbols
24
+
25
+ # @return [Integer] Base address.
26
+ attr_reader :address
27
+
28
+ # Instantiate an {Pwnlib::ELF::ELF} object.
29
+ #
30
+ # Will show checksec information to stdout.
31
+ #
32
+ # @param [String] path
33
+ # The path to the ELF file.
34
+ # @param [Boolean] checksec
35
+ # The checksec information will be printed to stdout after ELF loaded. Pass +checksec: false+ to disable this
36
+ # feature.
37
+ #
38
+ # @example
39
+ # ELF.new('/lib/x86_64-linux-gnu/libc.so.6')
40
+ # # RELRO: Partial RELRO
41
+ # # Stack: No canary found
42
+ # # NX: NX enabled
43
+ # # PIE: PIE enabled
44
+ # #=> #<Pwnlib::ELF::ELF:0x00559bd670dcb8>
45
+ def initialize(path, checksec: true)
46
+ @path = File.realpath(path)
47
+ @elf_file = ELFTools::ELFFile.new(File.open(path, 'rb'))
48
+ load_got
49
+ load_plt
50
+ load_symbols
51
+ @address = base_address
52
+ @load_addr = @address
53
+ @one_gadgets = nil
54
+ show_info(@path) if checksec
55
+ end
56
+
57
+ # Set the base address.
58
+ #
59
+ # Values in following tables will be changed simultaneously:
60
+ # got
61
+ # plt
62
+ # symbols
63
+ # one_gadgets
64
+ #
65
+ # @param [Integer] val
66
+ # Address to be changed to.
67
+ #
68
+ # @return [Integer]
69
+ # The new address.
70
+ def address=(val)
71
+ old = @address
72
+ @address = val
73
+ [@got, @plt, @symbols].compact.each do |tbl|
74
+ tbl.each_pair { |k, _| tbl[k] += val - old }
75
+ end
76
+ @one_gadgets&.map! { |off| off + val - old }
77
+ end
78
+
79
+ # Return the protection information, wrapper with color codes.
80
+ #
81
+ # @return [String]
82
+ # The checksec information.
83
+ def checksec
84
+ [
85
+ 'RELRO:'.ljust(10) + {
86
+ full: Rainbow('Full RELRO').green,
87
+ partial: Rainbow('Partial RELRO').yellow,
88
+ none: Rainbow('No RELRO').red
89
+ }[relro],
90
+ 'Stack:'.ljust(10) + {
91
+ true => Rainbow('Canary found').green,
92
+ false => Rainbow('No canary found').red
93
+ }[canary?],
94
+ 'NX:'.ljust(10) + {
95
+ true => Rainbow('NX enabled').green,
96
+ false => Rainbow('NX disabled').red
97
+ }[nx?],
98
+ 'PIE:'.ljust(10) + {
99
+ true => Rainbow('PIE enabled').green,
100
+ false => Rainbow(format('No PIE (0x%x)', address)).red
101
+ }[pie?]
102
+ ].join("\n")
103
+ end
104
+
105
+ # The method used in relro.
106
+ #
107
+ # @return [:full, :partial, :none]
108
+ def relro
109
+ return :none unless @elf_file.segment_by_type(:gnu_relro)
110
+ return :full if dynamic_tag(:bind_now)
111
+
112
+ flags = dynamic_tag(:flags)
113
+ return :full if flags && (flags.value & ::ELFTools::Constants::DF_BIND_NOW) != 0
114
+
115
+ flags1 = dynamic_tag(:flags_1)
116
+ return :full if flags1 && (flags1.value & ::ELFTools::Constants::DF_1_NOW) != 0
117
+
118
+ :partial
119
+ end
120
+
121
+ # Is this ELF file has canary?
122
+ #
123
+ # Actually judged by if +__stack_chk_fail+ in got symbols.
124
+ #
125
+ # @return [Boolean] Yes or not.
126
+ def canary?
127
+ @got.respond_to?('__stack_chk_fail') || @symbols.respond_to?('__stack_chk_fail')
128
+ end
129
+
130
+ # Is stack executable?
131
+ #
132
+ # @return [Boolean] Yes or not.
133
+ def nx?
134
+ !@elf_file.segment_by_type(:gnu_stack).executable?
135
+ end
136
+
137
+ # Is this ELF file a position-independent executable?
138
+ #
139
+ # @return [Boolean] Yes or not.
140
+ def pie?
141
+ @elf_file.elf_type == 'DYN'
142
+ end
143
+
144
+ # There's too many objects inside, let pry not so verbose.
145
+ # @return [String]
146
+ def inspect
147
+ "#<Pwnlib::ELF::ELF:#{::Pwnlib::Util::Fiddling.hex(__id__)}>"
148
+ end
149
+
150
+ # Yields the ELF's virtual address space for the specified string or regexp.
151
+ # Returns an Enumerator if no block given.
152
+ #
153
+ # @param [String, Regexp] needle
154
+ # The specified string to search.
155
+ #
156
+ # @return [Enumerator<Integer>]
157
+ # An enumerator for offsets in ELF's virtual address space.
158
+ #
159
+ # @example
160
+ # ELF.new('/bin/sh', checksec: false).find('ELF')
161
+ # #=> #<Enumerator: ...>
162
+ #
163
+ # ELF.new('/bin/sh', checksec: false).find(/E.F/).each { |i| puts i.hex }
164
+ # # 0x1
165
+ # # 0x11477
166
+ # # 0x1c84f
167
+ # # 0x1d5ee
168
+ # #=> true
169
+ def search(needle)
170
+ return enum_for(:search, needle) unless block_given?
171
+
172
+ load_address_fixup = @address - @load_addr
173
+ stream = @elf_file.stream
174
+ @elf_file.each_segments do |seg|
175
+ addr = seg.header.p_vaddr
176
+ memsz = seg.header.p_memsz
177
+ offset = seg.header.p_offset
178
+
179
+ stream.pos = offset
180
+ data = stream.read(memsz).ljust(seg.header.p_filesz, "\x00")
181
+
182
+ offset = 0
183
+ loop do
184
+ offset = data.index(needle, offset)
185
+ break if offset.nil?
186
+
187
+ yield (addr + offset + load_address_fixup)
188
+ offset += 1
189
+ end
190
+ end
191
+ true
192
+ end
193
+ alias find search
194
+
195
+ # Returns one-gadgets of glibc.
196
+ #
197
+ # @return [Array<Integer>]
198
+ # Returns array of one-gadgets, see examples.
199
+ #
200
+ # @example
201
+ # ELF::ELF.new('/lib/x86_64-linux-gnu/libc.so.6').one_gadgets[0]
202
+ # #=> 324293 # 0x4f2c5
203
+ #
204
+ # @example
205
+ # libc = ELF::ELF.new('/lib/x86_64-linux-gnu/libc.so.6')
206
+ # libc.one_gadgets[1]
207
+ # #=> 324386 # 0x4f322
208
+ #
209
+ # libc.address = 0x7fff7fff0000
210
+ # libc.one_gadgets[1]
211
+ # #=> 140735341130530 # 0x7fff8003f322
212
+ #
213
+ # @example
214
+ # libc = ELF::ELF.new('/lib/x86_64-linux-gnu/libc.so.6')
215
+ # context.log_level = :debug
216
+ # libc.one_gadgets[0]
217
+ # # [DEBUG] 0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
218
+ # # constraints:
219
+ # # rcx == NULL
220
+ # #=> 324293
221
+ def one_gadgets
222
+ return @one_gadgets if @one_gadgets
223
+
224
+ gadgets = OneGadget.gadgets(file: @path, details: true, level: 1)
225
+ @one_gadgets = gadgets.map { |g| g.offset + address }
226
+ @one_gadgets.instance_variable_set(:@gadgets, gadgets)
227
+
228
+ class << @one_gadgets
229
+ def [](idx)
230
+ super.tap { log.debug(@gadgets[idx].inspect) }
231
+ end
232
+
233
+ def first
234
+ self[0]
235
+ end
236
+
237
+ def last
238
+ self[-1]
239
+ end
240
+
241
+ include ::Pwnlib::Logger
242
+ end
243
+
244
+ @one_gadgets
245
+ end
246
+
247
+ private
248
+
249
+ def show_info(path)
250
+ log.info(path.inspect)
251
+ log.indented(checksec, level: ::Pwnlib::Logger::INFO)
252
+ end
253
+
254
+ # Get the dynamic tag with +type+.
255
+ # @return [ELFTools::Dynamic::Tag, nil]
256
+ def dynamic_tag(type)
257
+ dynamic = @elf_file.segment_by_type(:dynamic) || @elf_file.section_by_name('.dynamic')
258
+ return nil if dynamic.nil? # No dynamic table presents, might be statically linked.
259
+
260
+ dynamic.tag_by_type(type)
261
+ end
262
+
263
+ # Load got symbols
264
+ def load_got
265
+ @got = OpenStruct.new
266
+ sections_by_types(%i(rel rela)).each do |rel_sec|
267
+ symtab = @elf_file.section_at(rel_sec.header.sh_link)
268
+ next unless symtab.respond_to?(:symbol_at)
269
+
270
+ rel_sec.relocations.each do |rel|
271
+ symbol = symtab.symbol_at(rel.symbol_index)
272
+ next if symbol.nil? # Unusual case.
273
+
274
+ @got[symbol.name] = rel.header.r_offset.to_i
275
+ end
276
+ end
277
+ end
278
+
279
+ PLT_OFFSET = 0x10 # magic offset, correct in i386/amd64.
280
+ # Load all plt symbols.
281
+ def load_plt
282
+ # Unlike pwntools-python, which use unicorn emulating instructions to find plt(s).
283
+ # Here only use section information, which won't find any plt(s) when compile option '-Wl' is enabled.
284
+ #
285
+ # The implementation here same as pwntools-python 3.5, and supports i386 and amd64 only.
286
+ @plt = nil
287
+ plt_sec = @elf_file.section_by_name('.plt')
288
+ return log.warn('No PLT section found, PLT not loaded') if plt_sec.nil?
289
+
290
+ rel_sec = @elf_file.section_by_name('.rel.plt') || @elf_file.section_by_name('.rela.plt')
291
+ return log.warn('No REL.PLT section found, PLT not loaded') if rel_sec.nil?
292
+
293
+ symtab = @elf_file.section_at(rel_sec.header.sh_link)
294
+ return unless symtab.respond_to?(:symbol_at) # unusual case
295
+
296
+ @plt = OpenStruct.new
297
+ address = plt_sec.header.sh_addr.to_i + PLT_OFFSET
298
+ rel_sec.relocations.each do |rel|
299
+ symbol = symtab.symbol_at(rel.symbol_index)
300
+ next if symbol.nil? # unusual case
301
+
302
+ @plt[symbol.name] = address
303
+ address += PLT_OFFSET
304
+ end
305
+ end
306
+
307
+ # Load all exist symbols.
308
+ def load_symbols
309
+ @symbols = OpenStruct.new
310
+ @elf_file.each_sections do |section|
311
+ next unless section.respond_to?(:symbols)
312
+
313
+ section.each_symbols do |symbol|
314
+ # Don't care symbols without a name.
315
+ next if symbol.name.empty?
316
+ next if symbol.header.st_value.zero?
317
+
318
+ # TODO(david942j): handle symbols with the same name.
319
+ @symbols[symbol.name] = symbol.header.st_value.to_i
320
+ end
321
+ end
322
+ end
323
+
324
+ def sections_by_types(types)
325
+ types.map { |type| @elf_file.sections_by_type(type) }.flatten
326
+ end
327
+
328
+ def base_address
329
+ return 0 if pie?
330
+
331
+ # Find the min of PT_LOAD's p_vaddr
332
+ @elf_file.segments_by_type(:load)
333
+ .map { |seg| seg.header.p_vaddr }
334
+ .min
335
+ end
336
+
337
+ include ::Pwnlib::Logger
338
+ end
339
+ end
340
+ end