one_gadget 1.6.2 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +141 -94
  3. data/bin/one_gadget +4 -3
  4. data/lib/one_gadget/abi.rb +42 -22
  5. data/lib/one_gadget/builds/libc-2.19-397c84e78c14cbffba39a48184db482211df9fb3.rb +38 -0
  6. data/lib/one_gadget/builds/libc-2.19-4eda8ff01be3fba1c7bdd442a8690c3dc7397b6a.rb +44 -0
  7. data/lib/one_gadget/builds/libc-2.19-509ee0c9616c4c3ed81951501a8950e1f529bbff.rb +38 -0
  8. data/lib/one_gadget/builds/libc-2.19-6aff6d091954955fe931bb720a17708513aabda7.rb +41 -0
  9. data/lib/one_gadget/builds/libc-2.19-8d935a42f2f2a1149aa52d3098b32b1d5012cb67.rb +38 -0
  10. data/lib/one_gadget/builds/libc-2.19-a820f849dda0b99ed06dd59bb88404969b3a5f88.rb +41 -0
  11. data/lib/one_gadget/builds/libc-2.19-d9a10b8ef90300628dd0a3a535106967714d7328.rb +47 -0
  12. data/lib/one_gadget/builds/libc-2.21-169a143e9c40cfd9d09695333e45fd67743cd2d6.rb +37 -0
  13. data/lib/one_gadget/builds/libc-2.21-2e9718e58257bda1dc0d751665a3ee233bf606f2.rb +37 -0
  14. data/lib/one_gadget/builds/libc-2.23-29e38445a740bba5a77b86691e3c51a7e48dc79b.rb +46 -0
  15. data/lib/one_gadget/builds/libc-2.23-679ad41a6bc9e718a11a36cf9879cac97197e565.rb +37 -0
  16. data/lib/one_gadget/builds/libc-2.23-b5381a457906d279073822a5ceb24c4bfef94ddb.rb +37 -0
  17. data/lib/one_gadget/builds/libc-2.23-d10fbfd9328f5ffaca50aa93562cb3bfb618fbcc.rb +43 -0
  18. data/lib/one_gadget/builds/libc-2.23-dd5192a769e33ed6ca68a6ab5740ff9e8ec678a7.rb +46 -0
  19. data/lib/one_gadget/builds/libc-2.24-1f7bdfb9a24714835cee6e6597ea7aa782821371.rb +46 -0
  20. data/lib/one_gadget/builds/libc-2.24-206b2bb216b6cdb6b1be565a6fcd29f3862db060.rb +49 -0
  21. data/lib/one_gadget/builds/libc-2.24-26e84118fee5788eb5d8dda66b7e7f029d2c7800.rb +43 -0
  22. data/lib/one_gadget/builds/libc-2.24-43adbb1e7368c94fba1ba9020d8ef0808bff5bc4.rb +37 -0
  23. data/lib/one_gadget/builds/libc-2.24-497931f8d2346a6d0e300a65d8fc6106c6c88c15.rb +37 -0
  24. data/lib/one_gadget/builds/libc-2.24-4fa7401566d6b3e2c7ee5df3b4d85a01f85b595c.rb +37 -0
  25. data/lib/one_gadget/builds/libc-2.24-568d20b7e0d08bc282fb42ae405c7054e4209ede.rb +37 -0
  26. data/lib/one_gadget/builds/libc-2.24-5b72576ff331e93852355123afecdec70fd247b5.rb +49 -0
  27. data/lib/one_gadget/builds/libc-2.24-a4c01d397b6584f7040ef266b16a5d4da0b7a087.rb +43 -0
  28. data/lib/one_gadget/builds/libc-2.24-b81a06f0ac241c4aa8860602d9abcc903adbb675.rb +46 -0
  29. data/lib/one_gadget/builds/libc-2.24-be6d412ecc4816c46eb49e750b02f714a9131c4e.rb +46 -0
  30. data/lib/one_gadget/builds/libc-2.24-d2a8a8ac188a6c3bafa4813a3d2789240ee49489.rb +46 -0
  31. data/lib/one_gadget/builds/libc-2.24-dff06414a29b97b865ef938e06a7751fe8b1b2d0.rb +46 -0
  32. data/lib/one_gadget/builds/libc-2.24-e5dc6c0caa39828fa10ed37e642723a581acdb6d.rb +37 -0
  33. data/lib/one_gadget/builds/libc-2.24-fd0655c4d2073eda4235084e1d0e558f0251be8a.rb +37 -0
  34. data/lib/one_gadget/builds/libc-2.25-e5eb6347f0629b37bf698200022a683b7efb10ed.rb +37 -0
  35. data/lib/one_gadget/builds/libc-2.26-1c39b3b3faa2a2cbb0fa0b6845b29332562262d3.rb +37 -0
  36. data/lib/one_gadget/builds/libc-2.26-499b381aaf00ce85ee5d4a12770ea369b30d2a41.rb +52 -0
  37. data/lib/one_gadget/builds/libc-2.26-4cc84abfe1fd26a485fc2b1b954c281ce9d358fd.rb +52 -0
  38. data/lib/one_gadget/builds/libc-2.26-4ea852c9d6a5084b8b58509b3b3d37d3d8cddb90.rb +52 -0
  39. data/lib/one_gadget/builds/libc-2.26-6d2b609f0c8e7b338f767b08c5ac712fac809d31.rb +49 -0
  40. data/lib/one_gadget/builds/libc-2.26-fb587bc4429e7d1b0de31a3b9ee8ae78ee797eb0.rb +37 -0
  41. data/lib/one_gadget/builds/libc-2.27-0e188ec5f09c187a7a92784d4b97aa251b15a93c.rb +47 -0
  42. data/lib/one_gadget/builds/libc-2.27-53f40c1d2f3739ae017dcdcef1a17314786e3709.rb +38 -0
  43. data/lib/one_gadget/builds/libc-2.27-9dd0bb57f81671704475d1e5163405f7b4d4b454.rb +32 -0
  44. data/lib/one_gadget/builds/libc-2.28-44f5a3efb0e5733fa9d97e690cb36cd4c682bcdb.rb +41 -0
  45. data/lib/one_gadget/builds/libc-2.28-5784a31a1c26f6d2157e585205ebb63dd19ff90f.rb +41 -0
  46. data/lib/one_gadget/builds/libc-2.28-5b157f49586a3ca84d55837f97ff466767dd3445.rb +38 -0
  47. data/lib/one_gadget/builds/libc-2.28-6ee9454b96efa9e343f9e8105f2fa4529265ea05.rb +38 -0
  48. data/lib/one_gadget/emulators/aarch64.rb +176 -0
  49. data/lib/one_gadget/emulators/amd64.rb +1 -1
  50. data/lib/one_gadget/emulators/i386.rb +1 -1
  51. data/lib/one_gadget/emulators/instruction.rb +36 -7
  52. data/lib/one_gadget/emulators/lambda.rb +36 -25
  53. data/lib/one_gadget/emulators/processor.rb +94 -6
  54. data/lib/one_gadget/emulators/x86.rb +43 -95
  55. data/lib/one_gadget/error.rb +15 -3
  56. data/lib/one_gadget/fetcher.rb +3 -1
  57. data/lib/one_gadget/fetchers/aarch64.rb +41 -0
  58. data/lib/one_gadget/fetchers/amd64.rb +4 -2
  59. data/lib/one_gadget/fetchers/base.rb +35 -11
  60. data/lib/one_gadget/fetchers/i386.rb +2 -2
  61. data/lib/one_gadget/fetchers/x86.rb +23 -0
  62. data/lib/one_gadget/gadget.rb +63 -11
  63. data/lib/one_gadget/helper.rb +282 -203
  64. data/lib/one_gadget/one_gadget.rb +12 -4
  65. data/lib/one_gadget/version.rb +1 -1
  66. metadata +57 -6
@@ -5,12 +5,24 @@ module OneGadget
5
5
  class Error < StandardError
6
6
  end
7
7
 
8
- # Unsupported arguments of intructions.
9
- class UnsupportedInstructionArgumentsError < Error
8
+ # Super class of unsupported errors.
9
+ class UnsupportedError < Error
10
+ end
11
+
12
+ # Unsupported instruction.
13
+ class UnsupportedInstructionError < UnsupportedError
14
+ end
15
+
16
+ # Raises when arguments form of an instrution is invalid.
17
+ class InstructionArgumentError < Error
18
+ end
19
+
20
+ # Raises when form of arguments is valid but not supported.
21
+ class UnsupportedInstructionArgumentError < UnsupportedError
10
22
  end
11
23
 
12
24
  # Unsupported architecture.
13
- class UnsupportedArchitectureError < Error
25
+ class UnsupportedArchitectureError < UnsupportedError
14
26
  end
15
27
 
16
28
  # Argument error of ruby methods.
@@ -1,4 +1,5 @@
1
1
  require 'one_gadget/error'
2
+ require 'one_gadget/fetchers/aarch64'
2
3
  require 'one_gadget/fetchers/amd64'
3
4
  require 'one_gadget/fetchers/i386'
4
5
  require 'one_gadget/gadget'
@@ -27,6 +28,7 @@ module OneGadget
27
28
  def from_file(file)
28
29
  arch = OneGadget::Helper.architecture(file)
29
30
  klass = {
31
+ aarch64: OneGadget::Fetcher::AArch64,
30
32
  amd64: OneGadget::Fetcher::Amd64,
31
33
  i386: OneGadget::Fetcher::I386
32
34
  }[arch]
@@ -46,7 +48,7 @@ module OneGadget
46
48
  (gadgets[j].constraints - g.constraints).empty?
47
49
  end
48
50
  end
49
- res.sort_by!(&:offset)
51
+ res.sort_by(&:offset)
50
52
  end
51
53
  end
52
54
  extend ClassMethods
@@ -0,0 +1,41 @@
1
+ require 'one_gadget/emulators/aarch64'
2
+ require 'one_gadget/fetchers/base'
3
+
4
+ module OneGadget
5
+ module Fetcher
6
+ # Define common methods for gadget fetchers.
7
+ class AArch64 < Base
8
+ private
9
+
10
+ def emulator
11
+ OneGadget::Emulators::AArch64.new
12
+ end
13
+
14
+ # If str contains a branch instruction.
15
+ def branch?(str)
16
+ %w[b b.hi b.gt b.eq b.le b.ls b.lt b.ne b.cs].any? { |f| str.include?(' ' + f + ' ') }
17
+ end
18
+
19
+ def call_str
20
+ 'bl'
21
+ end
22
+
23
+ def bin_sh_offset
24
+ @bin_sh_offset ||= str_offset('/bin/sh')
25
+ end
26
+
27
+ def str_bin_sh?(str)
28
+ str.include?('$base') && str.include?(bin_sh_offset.to_s(16))
29
+ end
30
+
31
+ def str_sh?(str)
32
+ # XXX: hardcode -0x10 is bad
33
+ str.include?('$base') && str.include?((bin_sh_offset - 0x10).to_s(16))
34
+ end
35
+
36
+ def global_var?(str)
37
+ str.include?('$base')
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,10 +1,10 @@
1
1
  require 'one_gadget/emulators/amd64'
2
- require 'one_gadget/fetchers/base'
2
+ require 'one_gadget/fetchers/x86'
3
3
 
4
4
  module OneGadget
5
5
  module Fetcher
6
6
  # Fetcher for amd64.
7
- class Amd64 < OneGadget::Fetcher::Base
7
+ class Amd64 < OneGadget::Fetcher::X86
8
8
  private
9
9
 
10
10
  def emulator
@@ -36,6 +36,8 @@ module OneGadget
36
36
  next nil if jmp_at.nil?
37
37
 
38
38
  cand = cand[0..jmp_at]
39
+ next if cand.any? { |c| c.include?(call_str) }
40
+
39
41
  jmp_addr = cand.last.scan(/jmp\s+([\da-f]+)\s/)[0][0].to_i(16)
40
42
  dump = `#{objdump_cmd(start: jmp_addr, stop: jmp_addr + 100)}|egrep '[0-9a-f]+:'`
41
43
  remain = dump.lines.map(&:strip).reject(&:empty?)
@@ -11,6 +11,7 @@ module OneGadget
11
11
  # @param [String] file Absolute path of target libc.
12
12
  def initialize(file)
13
13
  @file = file
14
+ @arch = self.class.name.split('::').last.downcase.to_sym
14
15
  end
15
16
 
16
17
  # Do find gadgets in glibc.
@@ -29,7 +30,7 @@ module OneGadget
29
30
  gadgets << OneGadget::Gadget::Gadget.new(offset, options)
30
31
  end
31
32
  gadgets
32
- end.flatten.compact
33
+ end.flatten
33
34
  end
34
35
 
35
36
  # Fetch candidates that end with call exec*.
@@ -42,7 +43,7 @@ module OneGadget
42
43
  # @return [Array<String>]
43
44
  # Each +String+ returned is multi-lines of assembly code.
44
45
  def candidates(&block)
45
- cands = `#{objdump_cmd}|egrep 'call.*<exec[^+]*>$' -B 30`.split('--').map do |cand|
46
+ cands = `#{objdump_cmd}|egrep '#{call_str}.*<exec[^+]*>$' -B 30`.split('--').map do |cand|
46
47
  cand.lines.map(&:strip).reject(&:empty?).join("\n")
47
48
  end
48
49
  # remove all jmps
@@ -83,7 +84,7 @@ module OneGadget
83
84
  # arg[2] == NULL || [arg[2]] == NULL || arg[2] == envp
84
85
  arg1 = processor.argument(1).to_s
85
86
  arg2 = processor.argument(2).to_s
86
- cons = []
87
+ cons = processor.constraints
87
88
  cons << check_execve_arg(processor, arg1)
88
89
  return nil unless cons.all?
89
90
 
@@ -134,8 +135,9 @@ module OneGadget
134
135
  return nil if global_var?(arg) # we don't want base-related constraints
135
136
 
136
137
  args << arg
138
+ cons = processor.constraints + ["#{arg} == NULL"]
137
139
  # now arg is the constraint.
138
- { constraints: ["#{arg} == NULL"], effect: %(execl("/bin/sh", #{args.join(', ')})) }
140
+ { constraints: cons, effect: %(execl("/bin/sh", #{args.join(', ')})) }
139
141
  end
140
142
 
141
143
  def global_var?(_str); raise NotImplementedError
@@ -147,6 +149,9 @@ module OneGadget
147
149
  def str_sh?(_str); raise NotImplementedError
148
150
  end
149
151
 
152
+ def call_str; raise NotImplementedError
153
+ end
154
+
150
155
  def emulate(cmds)
151
156
  cmds.each_with_object(emulator) { |cmd, obj| break obj unless obj.process(cmd) }
152
157
  end
@@ -155,10 +160,20 @@ module OneGadget
155
160
  end
156
161
 
157
162
  def objdump_cmd(start: nil, stop: nil)
158
- cmd = %(objdump --no-show-raw-insn -w -d -M intel #{::Shellwords.escape(file)})
159
- cmd.concat(" --start-address #{start}") if start
160
- cmd.concat(" --stop-address #{stop}") if stop
161
- cmd
163
+ cmd = [objdump_bin, '--no-show-raw-insn', '-w', '-d', *objdump_options, file]
164
+ cmd.push('--start-address', start) if start
165
+ cmd.push('--stop-address', stop) if stop
166
+ ::Shellwords.join(cmd)
167
+ end
168
+
169
+ def objdump_bin
170
+ OneGadget::Helper.find_objdump(@arch).tap do |bin|
171
+ install_objdump_guide! if bin.nil?
172
+ end
173
+ end
174
+
175
+ def objdump_options
176
+ []
162
177
  end
163
178
 
164
179
  def slice_prefix(cands)
@@ -171,18 +186,27 @@ module OneGadget
171
186
  end
172
187
 
173
188
  # If str contains a branch instruction.
174
- def branch?(str)
175
- %w(jmp je jne jl jb ja jg).any? { |f| str.include?(f) }
189
+ def branch?(_str); raise NotImplementedError
176
190
  end
177
191
 
178
192
  def str_offset(str)
179
193
  IO.binread(file).index(str + "\x00") ||
180
- raise(Error::ArgumentError, "File #{file.inspect} doesn't contain string \"/bin/sh\", not glibc?")
194
+ raise(Error::ArgumentError, "File #{file.inspect} doesn't contain string #{str.inspect}, not glibc?")
181
195
  end
182
196
 
183
197
  def offset_of(assembly)
184
198
  assembly.scan(/^([\da-f]+):/)[0][0].to_i(16)
185
199
  end
200
+
201
+ def install_objdump_guide!
202
+ raise Error::UnsupportedArchitectureError, <<-EOS
203
+ Objdump that supports architecture #{@arch.to_s.inspect} is not found!
204
+ Please install the package 'binutils-multiarch' and try one_gadget again!
205
+
206
+ For Ubuntu users:
207
+ $ [sudo] apt install binutils-multiarch
208
+ EOS
209
+ end
186
210
  end
187
211
  end
188
212
  end
@@ -1,12 +1,12 @@
1
1
  require 'elftools'
2
2
 
3
3
  require 'one_gadget/emulators/i386'
4
- require 'one_gadget/fetchers/base'
4
+ require 'one_gadget/fetchers/x86'
5
5
 
6
6
  module OneGadget
7
7
  module Fetcher
8
8
  # Fetcher for i386.
9
- class I386 < OneGadget::Fetcher::Base
9
+ class I386 < OneGadget::Fetcher::X86
10
10
  private
11
11
 
12
12
  def candidates
@@ -0,0 +1,23 @@
1
+ require 'one_gadget/fetchers/base'
2
+
3
+ module OneGadget
4
+ module Fetcher
5
+ # Define common methods for gadget fetchers.
6
+ class X86 < Base
7
+ private
8
+
9
+ # If str contains a branch instruction.
10
+ def branch?(str)
11
+ %w[jmp je jne jl jb ja jg].any? { |f| str.include?(f) }
12
+ end
13
+
14
+ def objdump_options
15
+ %w[-M intel]
16
+ end
17
+
18
+ def call_str
19
+ 'call'
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,4 +1,5 @@
1
1
  require 'one_gadget/abi'
2
+ require 'one_gadget/emulators/lambda'
2
3
  require 'one_gadget/error'
3
4
 
4
5
  module OneGadget
@@ -7,11 +8,11 @@ module OneGadget
7
8
  # Information of a gadget.
8
9
  class Gadget
9
10
  # @return [Integer] The gadget's address offset.
10
- attr_accessor :offset
11
+ attr_reader :offset
11
12
  # @return [Array<String>] The constraints need for this gadget.
12
- attr_accessor :constraints
13
+ attr_reader :constraints
13
14
  # @return [String] The final result of this gadget.
14
- attr_accessor :effect
15
+ attr_reader :effect
15
16
 
16
17
  # Initialize method of {Gadget} instance.
17
18
  # @param [Integer] offset The relative address offset of this gadget.
@@ -28,22 +29,70 @@ module OneGadget
28
29
  # Show gadget in a pretty way.
29
30
  def inspect
30
31
  str = OneGadget::Helper.hex(offset)
31
- str += effect ? "\t#{effect}\n" : "\n"
32
+ str += effect ? " #{effect}\n" : "\n"
32
33
  unless constraints.empty?
33
34
  str += "#{OneGadget::Helper.colorize('constraints')}:\n "
34
- str += constraints.join("\n ")
35
+ str += merge_constraints.join("\n ")
35
36
  end
36
37
  str.gsub!(/0x[\da-f]+/) { |s| OneGadget::Helper.colorize(s, sev: :integer) }
37
- OneGadget::ABI.all.each { |reg| str.gsub!(reg, OneGadget::Helper.colorize(reg, sev: :reg)) }
38
+ OneGadget::ABI.all.each do |reg|
39
+ str.gsub!(/([^\w])(#{reg})([^\w])/, '\1' + OneGadget::Helper.colorize('\2', sev: :reg) + '\3')
40
+ end
38
41
  str + "\n"
39
42
  end
43
+
44
+ # @return [Float]
45
+ # The success probability of the constraints.
46
+ def score
47
+ @score ||= constraints.reduce(1.0) { |s, c| s * calculate_score(c) }
48
+ end
49
+
50
+ private
51
+
52
+ # REG: OneGadget::ABI.all
53
+ # IMM: [+-]0x[\da-f]+
54
+ # Identity: <REG><IMM>?
55
+ # Identity: [<Identity>]
56
+ # Expr: <REG> is the GOT address of libc
57
+ # Expr: writable: <Identity>
58
+ # Expr: <Identity> == NULL
59
+ # Expr: <Expr> || <Expr>
60
+ def calculate_score(cons)
61
+ return cons.split(' || ').map(&method(:calculate_score)).max if cons.include?(' || ')
62
+ return 0.9 if cons.include?('GOT address')
63
+ return 0.81 if cons.include?('writable')
64
+
65
+ expr = cons.gsub(' == NULL', ' == 0')
66
+ # raise Error::ArgumentError, cons unless expr.end_with?(' == 0')
67
+
68
+ identity = expr.slice(0...expr.rindex(' == 0'))
69
+ # Thank God we are already able to parse this
70
+ lmda = OneGadget::Emulators::Lambda.parse(identity)
71
+ # raise Error::ArgumentError, cons unless OneGadget::ABI.all.include?(lmda.obj)
72
+ # rax == 0 is easy; rax + 0x10 == 0 is damn hard.
73
+ return lmda.immi.zero? ? 0.9 : 0.1 if lmda.deref_count.zero?
74
+
75
+ # [sp+xx] == NULL is easy.
76
+ base = OneGadget::ABI.stack_register?(lmda.obj) ? 0 : 1
77
+ 0.9**(lmda.deref_count + base)
78
+ end
79
+
80
+ def merge_constraints
81
+ key = 'writable: '
82
+ w_cons, normal = constraints.partition { |c| c.start_with?(key) }
83
+ return normal if w_cons.empty?
84
+
85
+ w_cons.map! { |c| c[key.size..-1] }
86
+ ["address#{w_cons.size > 1 ? 'es' : ''} #{w_cons.join(', ')} #{w_cons.size > 1 ? 'are' : 'is'} writable"] +
87
+ normal
88
+ end
40
89
  end
41
90
 
42
91
  # Define class methods here.
43
92
  module ClassMethods
44
93
  # Path to the pre-build files.
45
94
  BUILDS_PATH = File.join(__dir__, 'builds').freeze
46
- # Cache.
95
+ # Record.
47
96
  BUILDS = Hash.new { |h, k| h[k] = [] }
48
97
  # Get gadgets from pre-defined corpus.
49
98
  # @param [String] build_id Desired build id.
@@ -51,8 +100,8 @@ module OneGadget
51
100
  # When local not found, try search in latest version?
52
101
  # @return [Array<Gadget::Gadget>?] Gadgets.
53
102
  def builds(build_id, remote: true)
54
- require_all if BUILDS.empty?
55
- return BUILDS[build_id] if BUILDS.key?(build_id)
103
+ ret = find_build(build_id)
104
+ return ret unless ret.nil?
56
105
  return build_not_found unless remote
57
106
 
58
107
  # fetch remote builds
@@ -107,10 +156,13 @@ module OneGadget
107
156
 
108
157
  private
109
158
 
110
- def require_all
111
- Dir.glob(File.join(BUILDS_PATH, '**', '*.rb')).each do |dic|
159
+ def find_build(id)
160
+ return BUILDS[id] if BUILDS.key?(id)
161
+
162
+ Dir.glob(File.join(BUILDS_PATH, "*-#{id}.rb")).each do |dic|
112
163
  require dic
113
164
  end
165
+ BUILDS[id] if BUILDS.key?(id)
114
166
  end
115
167
 
116
168
  def build_not_found
@@ -11,230 +11,309 @@ module OneGadget
11
11
  # Define some helpful methods here.
12
12
  module Helper
13
13
  # Format of build-id, 40 hex numbers.
14
- BUILD_ID_FORMAT = /[0-9a-f]{40}/
15
- # Define class methods here.
16
- module ClassMethods
17
- # Verify if `build_id` is a valid SHA1 hex format.
18
- # @param [String] build_id
19
- # BuildID.
20
- # @raise [Error::ArgumentError]
21
- # Raises error if invalid.
22
- # @return [void]
23
- def verify_build_id!(build_id)
24
- return if build_id =~ /\A#{OneGadget::Helper::BUILD_ID_FORMAT}\Z/
25
-
26
- raise OneGadget::Error::ArgumentError, format('invalid BuildID format: %p', build_id)
27
- end
14
+ BUILD_ID_FORMAT = /[0-9a-f]{40}/.freeze
28
15
 
29
- # Fetch lines start with '#'.
30
- # @param [String] file
31
- # Filename.
32
- # @return [Array<String>]
33
- # Lines of comments.
34
- def comments_of_file(file)
35
- File.readlines(file).map { |s| s[2..-1].rstrip if s.start_with?('# ') }.compact
36
- end
16
+ module_function
37
17
 
38
- # Get absolute path from relative path. Support symlink.
39
- # @param [String] path Relative path.
40
- # @return [String] Absolute path, with symlink resolved.
41
- # @example
42
- # abspath('/lib/x86_64-linux-gnu/libc.so.6')
43
- # #=> '/lib/x86_64-linux-gnu/libc-2.23.so'
44
- def abspath(path)
45
- Pathname.new(File.expand_path(path)).realpath.to_s
46
- end
18
+ # Checks if +build_id+ is a valid SHA1 hex format.
19
+ # @param [String] build_id
20
+ # BuildID.
21
+ # @raise [Error::ArgumentError]
22
+ # Raises error if invalid.
23
+ # @return [void]
24
+ def verify_build_id!(build_id)
25
+ return if build_id =~ /\A#{OneGadget::Helper::BUILD_ID_FORMAT}\Z/
47
26
 
48
- # Checks if the file of given path is a valid ELF file.
49
- #
50
- # @param [String] path Path to target file.
51
- # @return [Boolean] If the file is an ELF or not.
52
- # @example
53
- # valid_elf_file?('/etc/passwd')
54
- # => false
55
- # valid_elf_file?('/lib64/ld-linux-x86-64.so.2')
56
- # => true
57
- def valid_elf_file?(path)
58
- # A light-weight way to check if is a valid ELF file
59
- # Checks at least one phdr should present.
60
- File.open(path) { |f| ELFTools::ELFFile.new(f).each_segments.first }
61
- true
62
- rescue ELFTools::ELFError
63
- false
64
- end
27
+ raise OneGadget::Error::ArgumentError, format('invalid BuildID format: %p', build_id)
28
+ end
65
29
 
66
- # Checks if the file of given path is a valid ELF file
67
- # An error message will be shown if given path is not a valid ELF.
68
- #
69
- # @param [String] path Path to target file.
70
- # @return [void]
71
- # @raise [Error::ArgumentError] Raise exception if not a valid ELF.
72
- def verify_elf_file!(path)
73
- return if valid_elf_file?(path)
30
+ # Fetch lines start with '#'.
31
+ # @param [String] file
32
+ # Filename.
33
+ # @return [Array<String>]
34
+ # Lines of comments.
35
+ def comments_of_file(file)
36
+ File.readlines(file).map { |s| s[2..-1].rstrip if s.start_with?('# ') }.compact
37
+ end
74
38
 
75
- raise Error::ArgumentError, 'Not an ELF file, expected glibc as input'
76
- end
39
+ # Get absolute path from relative path. Support symlink.
40
+ # @param [String] path Relative path.
41
+ # @return [String] Absolute path, with symlink resolved.
42
+ # @example
43
+ # Helper.abspath('/lib/x86_64-linux-gnu/libc.so.6')
44
+ # #=> '/lib/x86_64-linux-gnu/libc-2.23.so'
45
+ def abspath(path)
46
+ Pathname.new(File.expand_path(path)).realpath.to_s
47
+ end
77
48
 
78
- # Get the Build ID of target ELF.
79
- # @param [String] path Absolute file path.
80
- # @return [String] Target build id.
81
- # @example
82
- # build_id_of('/lib/x86_64-linux-gnu/libc-2.23.so')
83
- # #=> '60131540dadc6796cab33388349e6e4e68692053'
84
- def build_id_of(path)
85
- File.open(path) { |f| ELFTools::ELFFile.new(f).build_id }
86
- end
49
+ # Checks if the file of given path is a valid ELF file.
50
+ #
51
+ # @param [String] path Path to target file.
52
+ # @return [Boolean] If the file is an ELF or not.
53
+ # @example
54
+ # Helper.valid_elf_file?('/etc/passwd')
55
+ # #=> false
56
+ # Helper.valid_elf_file?('/lib64/ld-linux-x86-64.so.2')
57
+ # #=> true
58
+ def valid_elf_file?(path)
59
+ # A light-weight way to check if is a valid ELF file
60
+ # Checks at least one phdr should present.
61
+ File.open(path) { |f| ELFTools::ELFFile.new(f).each_segments.first }
62
+ true
63
+ rescue ELFTools::ELFError
64
+ false
65
+ end
87
66
 
88
- # Disable colorize.
89
- # @return [void]
90
- def color_off!
91
- @disable_color = true
92
- end
67
+ # Checks if the file of given path is a valid ELF file.
68
+ #
69
+ # An error message will be shown if given path is not a valid ELF.
70
+ #
71
+ # @param [String] path Path to target file.
72
+ # @return [void]
73
+ # @raise [Error::ArgumentError] Raise exception if not a valid ELF.
74
+ def verify_elf_file!(path)
75
+ return if valid_elf_file?(path)
93
76
 
94
- # Enable colorize.
95
- # @return [void]
96
- def color_on!
97
- @disable_color = false
98
- end
77
+ raise Error::ArgumentError, 'Not an ELF file, expected glibc as input'
78
+ end
99
79
 
100
- # Is colorify output enabled?
101
- # @return [Boolean]
102
- # True or false.
103
- def color_enabled?
104
- # if not set, use tty to check
105
- return $stdout.tty? unless instance_variable_defined?(:@disable_color)
80
+ # Get the Build ID of target ELF.
81
+ # @param [String] path Absolute file path.
82
+ # @return [String] Target build id.
83
+ # @example
84
+ # Helper.build_id_of('/lib/x86_64-linux-gnu/libc-2.23.so')
85
+ # #=> '60131540dadc6796cab33388349e6e4e68692053'
86
+ def build_id_of(path)
87
+ File.open(path) { |f| ELFTools::ELFFile.new(f).build_id }
88
+ end
106
89
 
107
- !@disable_color
108
- end
90
+ # Disable colorize.
91
+ # @return [void]
92
+ def color_off!
93
+ @disable_color = true
94
+ end
109
95
 
110
- # Color codes for pretty print
111
- COLOR_CODE = {
112
- esc_m: "\e[0m",
113
- normal_s: "\e[38;5;203m", # red
114
- integer: "\e[38;5;189m", # light purple
115
- reg: "\e[38;5;82m", # light green
116
- warn: "\e[38;5;230m", # light yellow
117
- error: "\e[38;5;196m" # heavy red
118
- }.freeze
119
-
120
- # Wrapper color codes for pretty inspect.
121
- # @param [String] str Contents to colorize.
122
- # @param [Symbol] sev Specific which kind of color want to use, valid symbols are defined in +COLOR_CODE+.
123
- # @return [String] Wrapper with color codes.
124
- def colorize(str, sev: :normal_s)
125
- return str unless color_enabled?
126
-
127
- cc = COLOR_CODE
128
- color = cc.key?(sev) ? cc[sev] : ''
129
- "#{color}#{str.sub(cc[:esc_m], color)}#{cc[:esc_m]}"
130
- end
96
+ # Is colorize output enabled?
97
+ # @return [Boolean]
98
+ # True or false.
99
+ def color_enabled?
100
+ # if not set, use tty to check
101
+ return $stdout.tty? unless instance_variable_defined?(:@disable_color)
131
102
 
132
- # Fetch the latest release version's tag name.
133
- # @return [String] The tag name, in form +vx.x.x+.
134
- def latest_tag
135
- releases_url = 'https://github.com/david942j/one_gadget/releases/latest'
136
- @latest_tag ||= url_request(releases_url).split('/').last
137
- end
103
+ !@disable_color
104
+ end
138
105
 
139
- # Get the url which can fetch +filename+ from remote repo.
140
- # @param [String] filename
141
- # @return [String] The url.
142
- def url_of_file(filename)
143
- raw_file_url = 'https://raw.githubusercontent.com/david942j/one_gadget/@tag/@file'
144
- raw_file_url.sub('@tag', latest_tag).sub('@file', filename)
145
- end
106
+ # Color codes for pretty print
107
+ COLOR_CODE = {
108
+ esc_m: "\e[0m",
109
+ normal_s: "\e[38;5;203m", # red
110
+ integer: "\e[38;5;189m", # light purple
111
+ reg: "\e[38;5;82m", # light green
112
+ warn: "\e[38;5;230m", # light yellow
113
+ error: "\e[38;5;196m" # heavy red
114
+ }.freeze
146
115
 
147
- # Download the latest version of +file+ in +lib/one_gadget/builds/+ from remote repo.
148
- #
149
- # @param [String] file The filename desired.
150
- # @return [Tempfile] The temp file be created.
151
- def download_build(file)
152
- temp = Tempfile.new(['gadgets', file + '.rb'])
153
- url_request(url_of_file(File.join('lib', 'one_gadget', 'builds', file + '.rb')))
154
- temp.write(url_request(url_of_file(File.join('lib', 'one_gadget', 'builds', file + '.rb'))))
155
- temp.tap(&:close)
156
- end
116
+ # Wrap string with color codes for pretty inspect.
117
+ # @param [String] str Contents to colorize.
118
+ # @param [Symbol] sev Specify which kind of color to use, valid symbols are defined in {.COLOR_CODE}.
119
+ # @return [String] String wrapped with color codes.
120
+ def colorize(str, sev: :normal_s)
121
+ return str unless color_enabled?
157
122
 
158
- # Get the latest builds list from repo.
159
- # @return [Array<String>] List of build ids.
160
- def remote_builds
161
- @remote_builds ||= url_request(url_of_file('builds_list')).lines.map(&:strip)
162
- end
123
+ cc = COLOR_CODE
124
+ color = cc.key?(sev) ? cc[sev] : ''
125
+ "#{color}#{str.sub(cc[:esc_m], color)}#{cc[:esc_m]}"
126
+ end
163
127
 
164
- # Get request.
165
- # @param [String] url The url.
166
- # @return [String]
167
- # The request response body.
168
- # If the response is '302 Found', return the location in header.
169
- def url_request(url)
170
- uri = URI.parse(url)
171
- http = Net::HTTP.new(uri.host, uri.port)
172
- http.use_ssl = true
173
- http.verify_mode = ::OpenSSL::SSL::VERIFY_NONE
174
-
175
- request = Net::HTTP::Get.new(uri.request_uri)
176
-
177
- response = http.request(request)
178
- raise ArgumentError, "Fail to get response of #{url}" unless %w(200 302).include?(response.code)
179
-
180
- response.code == '302' ? response['location'] : response.body
181
- rescue NoMethodError, SocketError, ArgumentError => e
182
- p e
183
- nil
184
- end
128
+ # Fetch the latest release version's tag name.
129
+ # @return [String] The tag name, in form +vX.X.X+.
130
+ def latest_tag
131
+ releases_url = 'https://github.com/david942j/one_gadget/releases/latest'
132
+ @latest_tag ||= url_request(releases_url).split('/').last
133
+ end
185
134
 
186
- # Fetch the file archiecture of +file+.
187
- # @param [String] file The target ELF filename.
188
- # @return [Symbol]
189
- # Only supports architecture amd64 and i386 now.
190
- def architecture(file)
191
- f = File.open(file)
192
- str = ELFTools::ELFFile.new(f).machine
193
- {
194
- 'Advanced Micro Devices X86-64' => :amd64,
195
- 'Intel 80386' => :i386,
196
- 'ARM' => :arm,
197
- 'AArch64' => :aarch64,
198
- 'MIPS R3000' => :mips
199
- }[str] || :unknown
200
- rescue ELFTools::ELFError # not a valid ELF
201
- :invalid
202
- ensure
203
- f.close
204
- end
135
+ # Get the url which can fetch +filename+ from remote repo.
136
+ # @param [String] filename
137
+ # @return [String] The url.
138
+ def url_of_file(filename)
139
+ raw_file_url = 'https://raw.githubusercontent.com/david942j/one_gadget/@tag/@file'
140
+ raw_file_url.sub('@tag', latest_tag).sub('@file', filename)
141
+ end
205
142
 
206
- # Present number in hex format.
207
- # @param [Integer] val The number.
208
- # @param [Boolean] psign Need to show plus sign when +val >= 0+.
209
- # @return [String] string in hex format.
210
- # @example
211
- # hex(32) #=> 0x20
212
- # hex(32, psign: true) #=> +0x20
213
- # hex(-40) #=> -0x28
214
- # hex(0) #=> 0x0
215
- # hex(0, psign: true) #=> +0x0
216
- def hex(val, psign: false)
217
- return format("#{psign ? '+' : ''}0x%x", val) if val >= 0
218
-
219
- format('-0x%x', -val)
143
+ # Download the latest version of +file+ in +lib/one_gadget/builds/+ from remote repo.
144
+ #
145
+ # @param [String] file The filename desired.
146
+ # @return [Tempfile] The temp file be created.
147
+ def download_build(file)
148
+ temp = Tempfile.new(['gadgets', file + '.rb'])
149
+ temp.write(url_request(url_of_file(File.join('lib', 'one_gadget', 'builds', file + '.rb'))))
150
+ temp.tap(&:close)
151
+ end
152
+
153
+ # Get the latest builds list from repo.
154
+ # @return [Array<String>] List of build ids.
155
+ def remote_builds
156
+ @remote_builds ||= url_request(url_of_file('builds_list')).lines.map(&:strip)
157
+ end
158
+
159
+ # Get request.
160
+ # @param [String] url The url.
161
+ # @return [String]
162
+ # The request response body.
163
+ # If the response is +302 Found+, returns the location in header.
164
+ def url_request(url)
165
+ uri = URI.parse(url)
166
+ http = Net::HTTP.new(uri.host, uri.port)
167
+ http.use_ssl = true
168
+ http.verify_mode = ::OpenSSL::SSL::VERIFY_NONE
169
+
170
+ request = Net::HTTP::Get.new(uri.request_uri)
171
+
172
+ response = http.request(request)
173
+ raise ArgumentError, "Fail to get response of #{url}" unless %w[200 302].include?(response.code)
174
+
175
+ response.code == '302' ? response['location'] : response.body
176
+ rescue NoMethodError, SocketError, ArgumentError => e
177
+ OneGadget::Logger.error(e.message)
178
+ nil
179
+ end
180
+
181
+ # Fetch the ELF archiecture of +file+.
182
+ # @param [String] file The target ELF filename.
183
+ # @return [Symbol]
184
+ # Currently supports amd64, i386, arm, aarch64, and mips.
185
+ # @example
186
+ # Helper.architecture('/bin/cat')
187
+ # #=> :amd64
188
+ def architecture(file)
189
+ return :invalid unless File.exist?(file)
190
+
191
+ f = File.open(file)
192
+ str = ELFTools::ELFFile.new(f).machine
193
+ {
194
+ 'Advanced Micro Devices X86-64' => :amd64,
195
+ 'Intel 80386' => :i386,
196
+ 'ARM' => :arm,
197
+ 'AArch64' => :aarch64,
198
+ 'MIPS R3000' => :mips
199
+ }[str] || :unknown
200
+ rescue ELFTools::ELFError # not a valid ELF
201
+ :invalid
202
+ ensure
203
+ f.close if f
204
+ end
205
+
206
+ # Present number in hex format.
207
+ # @param [Integer] val
208
+ # The number.
209
+ # @param [Boolean] psign
210
+ # If needs to show the plus sign when +val >= 0+.
211
+ # @return [String]
212
+ # String in hex format.
213
+ # @example
214
+ # Helper.hex(32) #=> '0x20'
215
+ # Helper.hex(32, psign: true) #=> '+0x20'
216
+ # Helper.hex(-40) #=> '-0x28'
217
+ # Helper.hex(0) #=> '0x0'
218
+ # Helper.hex(0, psign: true) #=> '+0x0'
219
+ def hex(val, psign: false)
220
+ return format("#{psign ? '+' : ''}0x%x", val) if val >= 0
221
+
222
+ format('-0x%x', -val)
223
+ end
224
+
225
+ # Checks if a string can be converted into an integer.
226
+ # @param [String] str
227
+ # String to be checked.
228
+ # @return [Boolean]
229
+ # If +str+ can be converted into an integer.
230
+ # @example
231
+ # Helper.integer? '1234'
232
+ # #=> true
233
+ # Helper.integer? '0x1234'
234
+ # #=> true
235
+ # Helper.integer? '0xheapoverflow'
236
+ # #=> false
237
+ def integer?(str)
238
+ true if Integer(str)
239
+ rescue ArgumentError, TypeError
240
+ false
241
+ end
242
+
243
+ # Cross-platform way of finding an executable in +$PATH+.
244
+ #
245
+ # @param [String] cmd
246
+ # @return [String?]
247
+ # @example
248
+ # Helper.which('ruby')
249
+ # #=> "/usr/bin/ruby"
250
+ def which(cmd)
251
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
252
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
253
+ exts.each do |ext|
254
+ exe = File.join(path, "#{cmd}#{ext}")
255
+ return exe if File.executable?(exe) && !File.directory?(exe)
256
+ end
220
257
  end
258
+ nil
259
+ end
260
+
261
+ # Find objdump that supports architecture +arch+.
262
+ # @param [String] arch
263
+ # @return [String?]
264
+ # @example
265
+ # Helper.find_objdump(:amd64)
266
+ # #=> '/usr/bin/objdump'
267
+ # Helper.find_objdump(:aarch64)
268
+ # #=> '/usr/bin/aarch64-linux-gnu-objdump'
269
+ def find_objdump(arch)
270
+ [
271
+ which('objdump'),
272
+ which(arch_specific_objdump(arch))
273
+ ].find { |bin| objdump_arch_supported?(bin, arch) }
274
+ end
221
275
 
222
- # For checking a string is actually an integer.
223
- # @param [String] str String to be checked.
224
- # @return [Boolean] If +str+ can be converted into integer.
225
- # @example
226
- # Helper.integer? '1234'
227
- # # => true
228
- # Helper.integer? '0x1234'
229
- # # => true
230
- # Helper.integer? '0xheapoverflow'
231
- # # => false
232
- def integer?(str)
233
- true if Integer(str)
234
- rescue ArgumentError, TypeError
235
- false
276
+ # Checks if the given objdump supports certain architecture.
277
+ # @param [String] bin
278
+ # @param [Symbol] arch
279
+ # @return [Boolean]
280
+ # @example
281
+ # Helper.objdump_arch_supported?('/usr/bin/objdump', :i386)
282
+ # #=> true
283
+ def objdump_arch_supported?(bin, arch)
284
+ return false if bin.nil?
285
+
286
+ arch = objdump_arch(arch)
287
+ archs = `#{::Shellwords.join([bin, '--help'])}`.lines.find { |c| c.include?('supported architectures') }
288
+ return false if archs.nil?
289
+
290
+ archs.split.include?(arch)
291
+ end
292
+
293
+ # Converts to the architecture name shown in objdump's +--help+ command.
294
+ # @param [Symbol] arch
295
+ # @return [String]
296
+ # @example
297
+ # Helper.objdump_arch(:i386)
298
+ # #=> 'i386'
299
+ # Helper.objdump_arch(:amd64)
300
+ # #=> 'i386:x86-64'
301
+ def objdump_arch(arch)
302
+ case arch
303
+ when :amd64 then 'i386:x86-64'
304
+ else arch.to_s
236
305
  end
237
306
  end
238
- extend ClassMethods
307
+
308
+ # Returns the binary name of objdump.
309
+ # @param [Symbol] arch
310
+ # @return [String]
311
+ def arch_specific_objdump(arch)
312
+ {
313
+ aarch64: 'aarch64-linux-gnu-objdump',
314
+ amd64: 'x86_64-linux-gnu-objdump',
315
+ i386: 'i686-linux-gnu-objdump'
316
+ }[arch]
317
+ end
239
318
  end
240
319
  end