one_gadget 2.0.0 → 2.1.1

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +215 -65
  3. data/README.md +96 -22
  4. data/lib/one_gadget/abi.rb +41 -5
  5. data/lib/one_gadget/builds/libc-2.31-93b46e0027747153e336c3fd9431ce9a5d82ad00.rb +563 -0
  6. data/lib/one_gadget/builds/libc-2.35-891c1403437a4e30e684e0c8e34b87a09e4298e5.rb +434 -0
  7. data/lib/one_gadget/builds/libc-2.39-a1d1cf4badf1f5dfe57ff1d17bd692ccc6fcd5c5.rb +531 -0
  8. data/lib/one_gadget/builds/libc-2.39-cd8f5a207dd67aea370d2b471a54c3e56f44ab18.rb +531 -0
  9. data/lib/one_gadget/builds/libc-2.43-b50ceafbd17dc6bceee344a66671c7eaa152bef4.rb +15 -0
  10. data/lib/one_gadget/emulators/aarch64.rb +5 -2
  11. data/lib/one_gadget/emulators/amd64.rb +1 -0
  12. data/lib/one_gadget/emulators/arm.rb +27 -18
  13. data/lib/one_gadget/emulators/arm_family.rb +35 -160
  14. data/lib/one_gadget/emulators/conditional.rb +28 -22
  15. data/lib/one_gadget/emulators/constraints.rb +269 -0
  16. data/lib/one_gadget/emulators/data_processing.rb +167 -0
  17. data/lib/one_gadget/emulators/i386.rb +4 -6
  18. data/lib/one_gadget/emulators/instruction.rb +24 -1
  19. data/lib/one_gadget/emulators/lambda.rb +6 -4
  20. data/lib/one_gadget/emulators/mips.rb +289 -0
  21. data/lib/one_gadget/emulators/processor.rb +33 -455
  22. data/lib/one_gadget/emulators/register_file.rb +19 -10
  23. data/lib/one_gadget/emulators/riscv64.rb +265 -0
  24. data/lib/one_gadget/emulators/safe_calls.rb +9 -3
  25. data/lib/one_gadget/emulators/tracked_memory.rb +209 -0
  26. data/lib/one_gadget/emulators/x86.rb +32 -22
  27. data/lib/one_gadget/fetchers/aarch64.rb +0 -27
  28. data/lib/one_gadget/fetchers/amd64.rb +0 -24
  29. data/lib/one_gadget/fetchers/argument_resolution.rb +340 -0
  30. data/lib/one_gadget/fetchers/arm.rb +99 -44
  31. data/lib/one_gadget/fetchers/base.rb +142 -640
  32. data/lib/one_gadget/fetchers/candidate_walk.rb +150 -0
  33. data/lib/one_gadget/fetchers/disassembly.rb +252 -0
  34. data/lib/one_gadget/fetchers/dynamic_symbols.rb +104 -0
  35. data/lib/one_gadget/fetchers/i386.rb +5 -8
  36. data/lib/one_gadget/fetchers/mips.rb +478 -0
  37. data/lib/one_gadget/fetchers/objdump.rb +25 -3
  38. data/lib/one_gadget/fetchers/riscv64.rb +78 -0
  39. data/lib/one_gadget/fetchers/x86.rb +19 -0
  40. data/lib/one_gadget/fetchers.rb +22 -6
  41. data/lib/one_gadget/gadget.rb +27 -37
  42. data/lib/one_gadget/helper.rb +19 -5
  43. data/lib/one_gadget/one_gadget.rb +6 -0
  44. data/lib/one_gadget/version.rb +1 -1
  45. data/lib/one_gadget.rb +1 -1
  46. metadata +18 -6
  47. data/lib/one_gadget/builds/libc-2.26-2104f3d4ad5cf68603afbe7ba1a17f5ac99c5988.rb +0 -227
  48. data/lib/one_gadget/builds/libc-2.26-ddcc13122ddbfe5e5ef77d4ebe66d124ae5762c2.rb +0 -300
  49. data/lib/one_gadget/builds/libc-2.26-f65648a832414f2144ce795d75b6045a1ec2e252.rb +0 -199
@@ -15,15 +15,30 @@ require 'one_gadget/gadget'
15
15
  # <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
16
16
 
17
17
  build_id = File.basename(__FILE__, '.rb').split('-').last
18
+ OneGadget::Gadget.add(build_id, 310192,
19
+ constraints: ["readable: x22", "x0 != 0x1", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
20
+ effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
18
21
  OneGadget::Gadget.add(build_id, 310192,
19
22
  constraints: ["readable: x22", "x0 == 0x1", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
20
23
  effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
24
+ OneGadget::Gadget.add(build_id, 310196,
25
+ constraints: ["readable: x22", "x0 != 0x1", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
26
+ effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
21
27
  OneGadget::Gadget.add(build_id, 310196,
22
28
  constraints: ["readable: x22", "x0 == 0x1", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
23
29
  effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
30
+ OneGadget::Gadget.add(build_id, 310200,
31
+ constraints: ["readable: x22", "x0 != 0x1", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
32
+ effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
24
33
  OneGadget::Gadget.add(build_id, 310200,
25
34
  constraints: ["readable: x22", "x0 == 0x1", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
26
35
  effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
36
+ OneGadget::Gadget.add(build_id, 310208,
37
+ constraints: ["readable: x22", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
38
+ effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
39
+ OneGadget::Gadget.add(build_id, 310212,
40
+ constraints: ["readable: x22", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
41
+ effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
27
42
  OneGadget::Gadget.add(build_id, 310216,
28
43
  constraints: ["readable: x22", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
29
44
  effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
@@ -19,6 +19,8 @@ module OneGadget
19
19
  end
20
20
 
21
21
  # @see OneGadget::Emulators::X86#process!
22
+ # @param [String] cmd One line from result of objdump.
23
+ # @return [Boolean] If successfully processed.
22
24
  def process!(cmd)
23
25
  resolve_pending_branch(cmd)
24
26
  cmd = cmd.gsub(/#-?(0x)?[0-9a-f]+/) { |v| v[1..] }
@@ -27,8 +29,7 @@ module OneGadget
27
29
  return handle_branch(mnem, cmd) != :fail if branch_mnem?(mnem)
28
30
 
29
31
  inst, args = parse(cmd)
30
- sym = :"inst_#{inst.inst}"
31
- __send__(sym, *args) != :fail
32
+ __send__(inst.handler, *args) != :fail
32
33
  end
33
34
 
34
35
  # Supported instruction set.
@@ -61,6 +62,7 @@ module OneGadget
61
62
 
62
63
  # A landing pad marks where an indirect branch may arrive; nothing about the
63
64
  # state changes at one.
65
+ # @return [void]
64
66
  alias inst_bti inst_nop
65
67
 
66
68
  # Return the argument value of calling a function.
@@ -158,6 +160,7 @@ module OneGadget
158
160
 
159
161
  class << self
160
162
  # AArch64 is 64-bit.
163
+ # @return [Integer]
161
164
  def bits
162
165
  64
163
166
  end
@@ -9,6 +9,7 @@ module OneGadget
9
9
  class Amd64 < X86
10
10
  class << self
11
11
  # Bits.
12
+ # @return [Integer]
12
13
  def bits
13
14
  64
14
15
  end
@@ -28,11 +28,15 @@ module OneGadget
28
28
  end
29
29
 
30
30
  # Memoized bytes of +file+ (the target libc), shared across emulator instances.
31
+ # @param [String] file Path to the target libc.
32
+ # @return [String] Its bytes.
31
33
  def self.file_data(file)
32
34
  (@file_data ||= {})[file] ||= File.binread(file)
33
35
  end
34
36
 
35
37
  # @see OneGadget::Emulators::AArch64#process!
38
+ # @param [String] cmd One line from result of objdump.
39
+ # @return [Boolean] If successfully processed.
36
40
  def process!(cmd)
37
41
  resolve_pending_branch(cmd)
38
42
  line = cmd.strip
@@ -45,7 +49,7 @@ module OneGadget
45
49
  return __send__(:"inst_#{mnem}", rest) != :fail if %w[push pop].include?(mnem)
46
50
 
47
51
  inst, args = parse(body)
48
- __send__(:"inst_#{inst.inst}", *args) != :fail
52
+ __send__(inst.handler, *args) != :fail
49
53
  end
50
54
 
51
55
  # The flag-setting spelling of an instruction we model, which differs from
@@ -101,17 +105,16 @@ module OneGadget
101
105
  end
102
106
 
103
107
  # Settle Thumb vs A32 from a whole candidate, before any of it is emulated.
104
- # {#track_mode} can only learn from lines already seen, so the FIRST
105
- # instruction is judged on no evidence at all -- and when that instruction
106
- # reads +pc+, the bias it picks decides an address the constraints go on to
107
- # name. Applying the same evidence up front removes that dependence on
108
- # whatever happened to be processed first.
109
- #
110
- # A32 is left alone: it shows neither a width suffix nor a 2-byte stride, so
111
- # a genuinely A32 candidate keeps the whole-word bias. A single-instruction
112
- # Thumb candidate offers no evidence either and is no better served than
113
- # before.
108
+ # {#track_mode} learns only from lines already seen, so the first instruction
109
+ # would otherwise be judged on no evidence -- and when it reads +pc+, that
110
+ # decides an address the constraints go on to name.
114
111
  # @param [Array<String>] lines The candidate's objdump lines.
112
+ # @return [void]
113
+ # @example A 2-byte stride settles it as Thumb, where +pc+ reads four ahead
114
+ # of the instruction rather than eight.
115
+ # note_instruction_set(['4a1c0: ldr r0, [pc, #8]', '4a1c2: add r0, pc'])
116
+ # process('4a1c2: mov r0, pc')
117
+ # registers['r0'] #=> $base+0x4a1c6
115
118
  def note_instruction_set(lines)
116
119
  return if @thumb
117
120
 
@@ -133,12 +136,6 @@ module OneGadget
133
136
  @prev_addr = @cur_addr
134
137
  end
135
138
 
136
- # Split an objdump line into its instruction body and the literal-pool address
137
- # embedded in the trailing +@+ comment (used by PC-relative +ldr+).
138
- # @return [(String, Integer?)] The instruction body, and the literal address (or +nil+).
139
- # @example
140
- # split_line('2c626: ldr r2, [pc, #128] @ (2c6a8 <x>)')
141
- # #=> ['ldr r2, [pc, #128]', 0x2c6a8]
142
139
  # The instruction body a line carries, in the plain form the generic parser
143
140
  # expects, and the literal-pool address it points at. Both are decided by the
144
141
  # text alone (see {Processor.line_memo}).
@@ -151,9 +148,20 @@ module OneGadget
151
148
  end
152
149
  end
153
150
 
151
+ # Split an objdump line into its instruction body and the literal-pool address
152
+ # embedded in the trailing +@+ comment (used by PC-relative +ldr+).
153
+ # @return [(String, Integer?)] The instruction body, and the literal address (or +nil+).
154
+ # @example The same load, as objdump writes it from an ELF and from bytes --
155
+ # it states the address differently when it cannot name what is there.
156
+ # split_line('2c626: ldr r2, [pc, #128] @ (2c6a8 <x>)')
157
+ # #=> ['ldr r2, [pc, #128]', 0x2c6a8]
158
+ # split_line('2c626: ldr r2, [pc, #128] @ (0x2c6a8)')
159
+ # #=> ['ldr r2, [pc, #128]', 0x2c6a8]
160
+ # split_line('2c626: ldr.w r2, [pc, #128] @ 2c6a8')
161
+ # #=> ['ldr.w r2, [pc, #128]', 0x2c6a8]
154
162
  def split_line(line)
155
163
  body = line.sub(/\A[0-9a-f]+:\s*/, '')
156
- literal = body[/@\s*\(?([0-9a-f]+)\s/, 1]&.to_i(16)
164
+ literal = body[/@\s*\(?(?:0x)?([0-9a-f]+)(?:[\s)]|\z)/, 1]&.to_i(16)
157
165
  # Strip a trailing comment. The marker is whitespace-prefixed, which avoids
158
166
  # eating the +@@+ inside symbol names such as +<execve@@GLIBC_2.4>+.
159
167
  [body.sub(/\s+[@;].*\z/, '').strip, literal]
@@ -297,6 +305,7 @@ module OneGadget
297
305
 
298
306
  class << self
299
307
  # ARM (32-bit) is 32-bit.
308
+ # @return [Integer]
300
309
  def bits
301
310
  32
302
311
  end
@@ -13,10 +13,13 @@ module OneGadget
13
13
  # individual classes.
14
14
  class ArmFamily < Processor
15
15
  # ARM condition-code suffix (the +<cc>+ in +b<cc>+ / +b.<cc>+) mapped to a
16
- # shared {Conditional::RELATION} predicate. Decodes the opaque mnemonics once:
17
- # +hs+/+cs+ and +lo+/+cc+ are aliases; +mi+/+pl+ (sign bit) act as signed
18
- # +</+>=+. +vs+/+vc+ (overflow) have no constraint form and are absent, so a
19
- # branch on them maps to +nil+ and aborts the path. (x86 uses {X86::JCC}.)
16
+ # shared {Conditional::RELATION} predicate. (x86 uses {X86::JCC}.)
17
+ # @example Aliases decode alike, the sign bit reads as a signed comparison,
18
+ # and overflow has no constraint form -- a branch on it aborts the path.
19
+ # COND['hs'] #=> :uge
20
+ # COND['cs'] #=> :uge
21
+ # COND['mi'] #=> :slt
22
+ # COND['vs'] #=> nil
20
23
  COND = {
21
24
  'eq' => :eq, 'ne' => :ne,
22
25
  'hs' => :uge, 'cs' => :uge, 'lo' => :ult, 'cc' => :ult,
@@ -69,6 +72,23 @@ module OneGadget
69
72
  OneGadget::Helper.hex(m[1] == 'lsl' ? value << Integer(m[2]) : value >> Integer(m[2]))
70
73
  end
71
74
 
75
+ # +op2+ with the modifier this family may spell on it folded in. Yields for a
76
+ # modifier it does not model, so an unmodelled one aborts the candidate
77
+ # instead of being silently dropped.
78
+ # @param [String] op2 The operand the modifier applies to.
79
+ # @param [String, nil] mode The modifier, as written -- +nil+ for none.
80
+ # @return [String] The operand {Processor#arith} then reads.
81
+ # @example (aarch64, +x2+ holding +0x1+) A constant shift folds in, a
82
+ # sign-extension is taken whole, and a rotate is not modelled.
83
+ # modified_operand('x2', 'lsl 3') #=> '0x8'
84
+ # modified_operand('x2', 'sxtw') #=> 'x2'
85
+ # modified_operand('x2', 'ror 3') { :abort } #=> :abort
86
+ def modified_operand(op2, mode)
87
+ return op2 if mode.nil? || mode == 'sxtw'
88
+
89
+ shifted_operand(value_str(value_of(op2)), mode) || yield
90
+ end
91
+
72
92
  # +add+/+sub+, shared by both families. Each allows the 2-operand shorthand,
73
93
  # and each may carry a modifier on +op2+ -- a shift, or (aarch64) a
74
94
  # sign-extension of its low half.
@@ -77,94 +97,22 @@ module OneGadget
77
97
  # @param [String, nil] op2 The value to add, or nil in the 2-operand form.
78
98
  # @param [String, nil] mode A modifier applied to +op2+ (see {#modified_operand}).
79
99
  # @return [void]
80
- def inst_add(dst, src, op2 = nil, mode = nil) = arith(:+, dst, src, op2, mode)
81
-
82
- # +sub dst, src, op2+. See {#inst_add} for the operands.
83
- # @return [void]
84
- def inst_sub(dst, src, op2 = nil, mode = nil) = arith(:-, dst, src, op2, mode)
85
-
86
- # Add or subtract, and store the result.
87
- #
88
- # A sum of two values neither of which is known folds into no base+offset,
89
- # so it is named as the operation it is -- a candidate deriving a pointer
90
- # that way still says what the caller has to arrange. That is the only
91
- # fallback: an offset from a known base stays a base+offset, which the rest
92
- # of the emulator can resolve against tracked memory.
93
- # @param [Symbol] op +:++ or +:-+.
94
- # @return [void]
95
- # @raise [OneGadget::Error::UnsupportedInstructionArgumentError]
96
- # When the modifier, or the result, is not one this emulator can name.
97
- def arith(op, dst, src, op2, mode)
98
- check_register!(dst)
99
- src, op2 = shorthand(dst, src, op2)
100
- lhs = value_of(src)
101
- rhs = modified_operand(value_of(op2), mode) { raise_unsupported(op, dst, src, op2, mode) }
102
-
103
- result = offset_result(op, lhs, rhs)
104
- # The stack pointer has to stay an offset from itself: every tracked
105
- # stack slot is keyed on it, and a candidate that reads one back after
106
- # allocating a variable-size frame would be answered from the wrong
107
- # place. Such a frame also puts the array a gadget builds at an address
108
- # only a register the caller supplies decides, which no constraint this
109
- # emulator emits states.
110
- result ||= operation_result(op, lhs, rhs) unless dst == sp
111
- raise_unsupported(op, dst, src, op2) if result.nil?
112
-
113
- registers[dst] = result
114
- end
115
-
116
- # +lhs op rhs+ when the result is an offset from +lhs+'s base, which
117
- # {Lambda} expresses directly. +nil+ when it is not, leaving the caller to
118
- # name the operation instead.
119
- # @return [Lambda, Integer, nil]
120
- def offset_result(op, lhs, rhs)
121
- return lhs.send(op, rhs) if rhs.is_a?(Integer)
122
- # Adding a known offset to an unknown value is the same value shifted;
123
- # subtracting from one is not, so only addition commutes here.
124
- return rhs + lhs if op == :+ && lhs.is_a?(Integer)
125
-
126
- nil
127
- end
128
-
129
- # +value+ with an operand modifier applied. +nil+ +mode+ is the bare
130
- # operand. Yields, rather than returning, for a modifier this emulator does
131
- # not model, so an unmodelled one aborts instead of being silently dropped.
132
- # @example (aarch64) +add x0, x1, w2, sxtw+ -- +mode+ is +"sxtw"+
133
- # @example (arm) +add r0, r1, r2, lsl 3+ -- +mode+ is +"lsl 3"+
134
- # @return [Lambda, Integer]
135
- def modified_operand(value, mode)
136
- return value if mode.nil? || mode == 'sxtw' # a sign-extension we take whole
137
-
138
- shifted = shifted_operand(value_str(value), mode)
139
- return yield if shifted.nil?
140
-
141
- Integer(shifted)
100
+ def inst_add(dst, src, op2 = nil, mode = nil)
101
+ arith(:+, dst, src, modified_operand(op2, mode) { raise_unsupported(:+, dst, src, op2, mode) })
142
102
  end
143
103
 
144
- # Apply a data-processing instruction and store its result. Both families
145
- # allow the 2-operand shorthand, so +src+ may be the only operand given.
146
- # @param [String] name The mnemonic, and the {DATA_OPS} key naming its operator.
147
- # @param [String] dst The destination register.
148
- # @param [String] src The left operand, or the only one given (see {#shorthand}).
149
- # @param [String, nil] op2 The right operand, or nil in the 2-operand form.
104
+ # +sub dst, src, op2+. See {#inst_add} for the operands.
150
105
  # @return [void]
151
- # @raise [OneGadget::Error::UnsupportedInstructionArgumentError]
152
- # When the result is nothing this emulator can name.
153
- def data_op(name, dst, src, op2)
154
- check_register!(dst)
155
- src, op2 = shorthand(dst, src, op2)
156
- result = operation_result(DATA_OPS.fetch(name), value_of(src), value_of(op2))
157
- raise_unsupported(name, dst, src, op2) if result.nil?
158
-
159
- # A shift can push bits past the register width, which the arbitrary-
160
- # precision fold above would otherwise keep.
161
- registers[dst] = result.is_a?(Integer) ? result & width_mask : result
106
+ def inst_sub(dst, src, op2 = nil, mode = nil)
107
+ arith(:-, dst, src, modified_operand(op2, mode) { raise_unsupported(:-, dst, src, op2, mode) })
162
108
  end
163
109
 
164
110
  # Each {DATA_OPS} mnemonic handled the one way, since they differ only in the
165
- # operator applied (see {#data_op} for the operands).
111
+ # operator applied (see {Processor#data_op} for the operands).
166
112
  %w[and orr eor lsl lsr].each do |name|
167
- define_method(:"inst_#{name}") { |dst, src, op2 = nil| data_op(name, dst, src, op2) }
113
+ define_method(:"inst_#{name}") do |dst, src, op2 = nil|
114
+ data_op(DATA_OPS.fetch(name), dst, src, op2, name:)
115
+ end
168
116
  end
169
117
 
170
118
  # +bic dst, src, op2+ clears the bits +op2+ sets, which is +and+ against its
@@ -176,7 +124,8 @@ module OneGadget
176
124
  # @return [void]
177
125
  def inst_bic(dst, src, op2 = nil)
178
126
  src, op2 = shorthand(dst, src, op2)
179
- data_op('and', dst, src, OneGadget::Helper.hex(complement('bic', op2, dst, src, op2)))
127
+ data_op(DATA_OPS.fetch('and'), dst, src,
128
+ OneGadget::Helper.hex(complement('bic', op2, dst, src, op2)), name: 'and')
180
129
  end
181
130
 
182
131
  # +mvn dst, op2+ is that complement on its own.
@@ -189,51 +138,6 @@ module OneGadget
189
138
  registers[dst] = complement('mvn', op2, dst, op2)
190
139
  end
191
140
 
192
- # +op2+ with every bit flipped. Only a concrete value has a complement this
193
- # emulator can name; a symbolic one aborts rather than being recorded as a
194
- # mask it isn't.
195
- # @param [String] name The mnemonic to report an abort against.
196
- # @param [String] op2 The operand to complement.
197
- # @param [Array<String>] reported The operands to name in that abort.
198
- # @return [Integer] +op2+ complemented, within the register width.
199
- def complement(name, op2, *reported)
200
- value = value_of(op2)
201
- raise_unsupported(name, *reported) unless value.is_a?(Integer)
202
-
203
- ~value & width_mask
204
- end
205
-
206
- # Every bit of a register, for masking a result back to its width.
207
- # @return [Integer]
208
- def width_mask = (1 << self.class.bits) - 1
209
-
210
- # The value of an operand. {Arm} overrides it for +pc+, whose value depends
211
- # on the address of the instruction reading it.
212
- # @param [String] arg The operand, as written.
213
- # @return [OneGadget::Emulators::Lambda, Integer] Its current value.
214
- def value_of(arg) = arg_to_lambda(arg)
215
-
216
- # Expand a 2-operand data-processing form into its (src, op2) operands:
217
- # +add dst, op2+ is shorthand for +add dst, dst, op2+, while an explicit
218
- # 3-operand form is passed through unchanged.
219
- # @param [String] dst The destination register, which the 2-operand form
220
- # also reads as its left operand.
221
- # @param [String] src The left operand, or the right one in the 2-operand form.
222
- # @param [String, nil] op2 The right operand, or nil in the 2-operand form.
223
- # @return [(String, String)] The left and right operands.
224
- # @example
225
- # shorthand('r0', 'r4', nil) # 2-operand: add r0, r4
226
- # #=> ['r0', 'r4']
227
- # shorthand('r0', 'r4', '8') # 3-operand: add r0, r4, 8
228
- # #=> ['r4', '8']
229
- def shorthand(dst, src, op2)
230
- op2.nil? ? [dst, src] : [src, op2]
231
- end
232
-
233
- # An instruction with no effect this emulator models anything of.
234
- # @return [void]
235
- def inst_nop(*); end
236
-
237
141
  # A memory barrier orders accesses without changing any value, so a path
238
142
  # crossing one carries on unchanged. The operand naming its scope (+dmb ish+)
239
143
  # says which accesses, not what they hold.
@@ -249,21 +153,6 @@ module OneGadget
249
153
  dispatch_safe_call(addr)
250
154
  end
251
155
 
252
- # Track a store: write +values+ (one per word from +dst_l+) into the stack
253
- # {#resolve_address} resolves +dst_l+ to, and require +dst_l+
254
- # writable -- unless it is a pure +sp+ store. +sp+ is invariantly the
255
- # writable stack; the frame pointer only conventionally is, so a store
256
- # through it stays a real precondition (like amd64's +writable: rbp+imm+).
257
- # Shared by +str+ (one value) and +stp+ (two).
258
- # @param [OneGadget::Emulators::Lambda] dst_l The destination, zero-deref.
259
- # @param [Array<OneGadget::Emulators::Lambda, Integer>] values One per word.
260
- # @return [void]
261
- def track_write(dst_l, *values)
262
- stack, offset = resolve_address(dst_l)
263
- values.each_with_index { |v, i| stack[offset + size_t * i] = v } if stack
264
- add_writable(dst_l) unless stack.equal?(sp_based_stack)
265
- end
266
-
267
156
  # A byte load. The address is read like any other, so what the caller has to
268
157
  # arrange about it is recorded the same way -- but one byte of a word is not
269
158
  # a value this emulator can name, so the register holds what a call would
@@ -281,20 +170,6 @@ module OneGadget
281
170
  def handle_cbz(ops, negate:)
282
171
  branch_on_zero(ops[1].to_i(16), ops[0], negate:)
283
172
  end
284
-
285
- # Replace register tokens that currently hold a concrete integer with that
286
- # integer, so a register-indexed memory operand becomes an offset one the
287
- # Lambda parser handles.
288
- # @example
289
- # # with the index register currently holding 0xd8
290
- # resolve_int_regs('[r8, r2]') #=> '[r8, 0xd8]'
291
- # resolve_int_regs('[x8, x2]') #=> '[x8, 0xd8]'
292
- def resolve_int_regs(str)
293
- str.gsub(/[a-z]+\d*/) do |tok|
294
- v = registers[tok] if register?(tok)
295
- v.is_a?(Integer) ? OneGadget::Helper.hex(v) : tok
296
- end
297
- end
298
173
  end
299
174
  end
300
175
  end
@@ -11,10 +11,6 @@ module OneGadget
11
11
  # actual taken/not-taken path (see {OneGadget::Fetchers::Base#candidates}), and
12
12
  # the emulator turns the branch decision into a gadget constraint.
13
13
  #
14
- # Branches are resolved with one line of look-ahead: at the branch we record a
15
- # pending decision, and on the next line we compare that line's address to the
16
- # branch target to learn whether the stitched path took the branch.
17
- #
18
14
  # The including class (an {OneGadget::Emulators::Processor} subclass) must
19
15
  # provide +registers+ and +register?+ (operand lookup), +operands(cmd)+ (the
20
16
  # arch's operand splitter), +self.class.bits+ (32/64, for the signedness cast),
@@ -27,8 +23,11 @@ module OneGadget
27
23
  module Conditional
28
24
  # Taken-semantics of each supported branch condition, keyed by a predicate
29
25
  # named after the comparison it encodes (the LLVM +icmp+ names): a leading
30
- # +u+ = unsigned, +s+ = signed. Value is +[relation, signedness]+, where
31
- # signedness (+nil+/+:u+/+:s+) selects the operand cast.
26
+ # +u+ = unsigned, +s+ = signed.
27
+ # @example The value is the relation, and the cast its operands take.
28
+ # RELATION[:eq] #=> ['==', nil]
29
+ # RELATION[:ult] #=> ['<', :u]
30
+ # RELATION[:sge] #=> ['>=', :s]
32
31
  # :eq :ne equality
33
32
  # :ult :ule :ugt :uge unsigned < <= > >=
34
33
  # :slt :sle :sgt :sge signed < <= > >=
@@ -42,11 +41,12 @@ module OneGadget
42
41
  NEGATE = { '==' => '!=', '!=' => '==', '>=' => '<', '<' => '>=', '>' => '<=', '<=' => '>' }.freeze
43
42
 
44
43
  # The flag-setting compares we model, keyed by the ALU operation the compare
45
- # performs -- its flags reflect that result. Each entry says whether magnitude
46
- # conditions (anything beyond +eq+/+ne+) are sound afterwards (+ordered+) and
47
- # names the method that renders its constraint text. An arch maps its own
48
- # mnemonics onto these ops (its +COMPARES+), so adding an arch needs no change
49
- # here; adding a genuinely new ALU op means one entry plus its +render_*+ method.
44
+ # performs. An arch maps its own mnemonics onto these (its +COMPARES+), so
45
+ # adding one needs no change here.
46
+ # @example Whether magnitude conditions are sound after it, and what renders
47
+ # its constraint text.
48
+ # COMPARE_OPS[:sub] #=> { ordered: true, render: :render_sub }
49
+ # COMPARE_OPS.keys #=> [:sub, :add, :and]
50
50
  COMPARE_OPS = {
51
51
  sub: { ordered: true, render: :render_sub }, # subtraction: flags from lhs - rhs
52
52
  add: { ordered: true, render: :render_add }, # addition: flags from lhs + rhs
@@ -56,9 +56,9 @@ module OneGadget
56
56
  # A value comparison's zero literal, always hex. Pointer operands render
57
57
  # +NULL+ (chosen by the arg resolvers); every value-context zero here is
58
58
  # +0x0+, so a bare +0+ (and objdump's arch-specific +#0+ vs +0x0+) never leaks
59
- # into a constraint.
59
+ # into a constraint. Recorded here and read back by {Constraints}, whose drop
60
+ # rules recognise a compare against zero by it.
60
61
  ZERO = '0x0'
61
- private_constant :ZERO
62
62
 
63
63
  # Record a compare so a following conditional branch can be rendered.
64
64
  # Normally reached through {#handle_compare}; call it directly only when an
@@ -188,6 +188,7 @@ module OneGadget
188
188
  # # fall-through path emits x0 != 0 ; taken path emits x0 == 0
189
189
  # @example x86 reuses it for +jrcxz+/+jecxz+/+jcxz+ (always branch-if-zero)
190
190
  # branch_on_zero(0x4a200, 'rcx', negate: false)
191
+ # @return [true] Registering the branch is how the line is handled.
191
192
  def branch_on_zero(target, operand, negate:)
192
193
  reg = operand_str(operand)
193
194
  hit = negate ? '!=' : '==' # taken (not negated) => reg == 0
@@ -205,6 +206,7 @@ module OneGadget
205
206
  # @example aarch64 +tbz w0, #4, 4a200+ - branch taken when bit 4 of +w0+ is 0
206
207
  # branch_on_bit(0x4a200, 'w0', 4, negate: false) #=> true
207
208
  # # taken path emits (w0 & 0x10) == 0 ; fall-through emits (w0 & 0x10) != 0
209
+ # @return [true] Registering the branch is how the line is handled.
208
210
  def branch_on_bit(target, operand, bit, negate:)
209
211
  reg = operand_str(operand)
210
212
  mask = OneGadget::Helper.hex(1 << bit)
@@ -227,6 +229,9 @@ module OneGadget
227
229
  # end
228
230
  # # if the previous line did +branch_on_compare(:ne, 0x4a200)+ and this
229
231
  # # +cmd+ sits at 0x4a200, the branch was taken; otherwise it fell through
232
+ # @return [void]
233
+ # @raise [OneGadget::Error::InfeasiblePathError]
234
+ # When the resolved relation cannot hold with the ones already recorded.
230
235
  def resolve_pending_branch(cmd)
231
236
  return if @pending.nil?
232
237
 
@@ -244,6 +249,8 @@ module OneGadget
244
249
  # each register's current value, so two constraints printing the same left
245
250
  # side really are about the same tracked value (and a differing signedness
246
251
  # cast is part of that text, keeping incomparable ones apart).
252
+ # @param [String] expr The left side, as rendered.
253
+ # @return [Array<(String, String, String)>] The comparisons recorded on it.
247
254
  def comparisons_on(expr)
248
255
  @constraints.filter_map { |type, obj| obj if type == :cmp && obj.first == expr }
249
256
  end
@@ -304,15 +311,14 @@ module OneGadget
304
311
  ["#{cast}#{lhs}", op, rhs]
305
312
  end
306
313
 
307
- # Addition (+:add+): the flags reflect +lhs + rhs+ compared against 0 -- but
308
- # only for a signed or equality condition. An *unsigned* condition reads the
309
- # carry out of the addition instead, which holds over a whole range of +lhs+,
310
- # not just the one value making the sum zero: the carry is set exactly when
311
- # +lhs + rhs+ wraps, i.e. when +lhs >= -rhs+. So the condition is +lhs+
312
- # against +-rhs+, the form the subtractive compares already produce.
313
- # @example glibc's "the syscall didn't return -errno" check, +cmn x0, 0x1000+
314
- # followed by a not-taken +b.hi+, is +(u64)x0 <= 0xfffffffffffff000+ --
315
- # satisfied by x0 == 0, which "(x0 + 0x1000) <= 0" would wrongly exclude.
314
+ # Addition (+:add+): the flags reflect +lhs + rhs+ against 0, except under an
315
+ # unsigned condition, which reads the carry instead -- set exactly when the
316
+ # sum wraps, that is when +lhs >= -rhs+.
317
+ # @example Read as an equality, then unsigned -- the second is glibc's "the
318
+ # syscall didn't return -errno" check, which +x0 == 0+ satisfies and
319
+ # +(x0 + 0x1000) <= 0+ would wrongly exclude.
320
+ # render_add(nil, 'x0', '0x1000', :ne, nil) #=> ['(x0 + 0x1000)', :ne, '0x0']
321
+ # render_add(nil, 'x0', '0x1000', :le, :u) #=> ['x0', :le, '0xfffffffffffff000']
316
322
  def render_add(cast, lhs, rhs, op, sign)
317
323
  return ["#{cast}(#{lhs} + #{rhs})", op, ZERO] unless sign == :u && negatable?(rhs)
318
324