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
@@ -0,0 +1,269 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'one_gadget/emulators/conditional'
4
+ require 'one_gadget/emulators/lambda'
5
+
6
+ module OneGadget
7
+ module Emulators
8
+ # What a candidate requires of its caller, collected while it is emulated and
9
+ # rendered once it ends. A path that dereferences a pointer, stores through an
10
+ # address, or reaches a call that reads one records the requirement here rather
11
+ # than assuming it holds; {#constraints} then drops the ones another already
12
+ # implies and names what is left. Mixed into {Processor}.
13
+ module Constraints
14
+ # Marks a register holding whatever a call returned or left behind; see
15
+ # {Processor#clobber_caller_saved}.
16
+ CLOBBERED = '$clobbered'
17
+
18
+ # Constraint types whose payload is an address {Lambda} asserting the target
19
+ # is mapped -- +:writable+ (a store target) and +:readable+ (an unconditional
20
+ # dereference, see {#finalize_deferred_reads}). Both are keyed, offset-
21
+ # normalised, and imply non-NULL identically; they differ only in how they
22
+ # render (see {#render_constraint}). The remaining type, +:raw+, carries a
23
+ # ready-made constraint string that keys on itself, and +:cmp+ a comparison
24
+ # recorded as its +[lhs, operator, rhs]+ parts (see {Conditional}), so it can
25
+ # be inspected rather than re-parsed from the rendered text.
26
+ ADDRESS_TYPES = %i[writable readable].freeze
27
+
28
+ # {SafeCalls} requirements naming what a callee does with a pointer argument,
29
+ # each recorded as something the caller must arrange (see {#record_pointer}),
30
+ # as opposed to a precondition read off the value as it stands.
31
+ POINTER_REQUIREMENTS = %i[writable deref nullable_deref null].freeze
32
+
33
+ # The {POINTER_REQUIREMENTS} a NULL argument already satisfies: both ask for
34
+ # a pointer the callee will leave alone, and NULL is how that is asked for.
35
+ NULLABLE_REQUIREMENTS = %i[nullable_deref null].freeze
36
+
37
+ # @return [Array<String>] Where each descriptor this candidate closes is read
38
+ # from, in the order they are closed, without repeats.
39
+ def closed_fds
40
+ @closed_fds.uniq
41
+ end
42
+
43
+ # @return [Array<String>]
44
+ # Extra constraints found during execution.
45
+ def constraints
46
+ finalize_deferred_reads
47
+ return [] if @constraints.empty?
48
+
49
+ # An address constraint is keyed by its base register (deref-0) or full
50
+ # expression (compound); several through one base (e.g. stores at reg+0x0
51
+ # and reg+0x8) impose the same requirement, so keep just the smallest
52
+ # offset (sort ascending, then uniq keeps that first).
53
+ cons = @constraints.sort_by { |type, obj| address_deref0?(type, obj) ? obj.immi : 0 }
54
+ .uniq { |type, obj| constraint_key(type, obj) }
55
+ cons = drop_restated_null(drop_implied_nonzero(cons))
56
+ cons.map { |type, obj| render_constraint(type, obj) }.sort
57
+ end
58
+
59
+ # Whether +(type, obj)+ is an address constraint on a bare (deref-0) target,
60
+ # i.e. one carrying a base register and offset to normalise.
61
+ # @param [Symbol] type The constraint's type.
62
+ # @param [Object] obj Its payload.
63
+ # @return [Boolean]
64
+ def address_deref0?(type, obj)
65
+ ADDRESS_TYPES.include?(type) && obj.deref_count.zero?
66
+ end
67
+
68
+ # De-duplication key: an address constraint collapses per (type, base) so
69
+ # constraints of different types on the same register stay distinct; a raw
70
+ # constraint keys on its own text.
71
+ # @param [Symbol] type The constraint's type.
72
+ # @param [Object] obj Its payload.
73
+ # @return [Object] Equal for two constraints that impose the same requirement.
74
+ def constraint_key(type, obj)
75
+ return obj unless ADDRESS_TYPES.include?(type)
76
+
77
+ [type, obj.deref_count.zero? ? obj.obj.to_s : obj.to_s]
78
+ end
79
+
80
+ # Render a constraint to its output string.
81
+ # @param [Symbol] type The constraint's type.
82
+ # @param [Object] obj Its payload.
83
+ # @return [String] The constraint as reported.
84
+ def render_constraint(type, obj)
85
+ case type
86
+ when :writable then "writable: #{obj}"
87
+ when :readable then "readable: #{obj}"
88
+ when :cmp then obj.join(' ')
89
+ else obj
90
+ end
91
+ end
92
+
93
+ # Drop a "<reg> != 0x0" branch constraint that another constraint already
94
+ # implies: an address constraint (+writable: <reg>+imm+ store target, or
95
+ # +readable: <reg>+) forces <reg> to be a valid (mapped, non-NULL) pointer,
96
+ # so a NULL-check branch on the same register adds nothing. Keeps the
97
+ # emitted set minimal.
98
+ # @param [Array<[Symbol, Object]>] cons The de-duplicated constraint list.
99
+ # @return [Array<[Symbol, Object]>]
100
+ def drop_implied_nonzero(cons)
101
+ nonzero_regs = cons.filter_map do |type, obj|
102
+ obj.obj.to_s if address_deref0?(type, obj)
103
+ end
104
+ return cons if nonzero_regs.empty?
105
+
106
+ cons.reject do |type, obj|
107
+ type == :cmp && obj[1] == '!=' && obj[2] == Conditional::ZERO && nonzero_regs.include?(obj[0])
108
+ end
109
+ end
110
+
111
+ # Drop a "<X> == 0x0" branch constraint that a NULL requirement on the same
112
+ # value already states (see {#require_null}). Both ask for the same zero, and
113
+ # the one naming it NULL is the one that says what the zero is for.
114
+ # @param [Array<[Symbol, Object]>] cons The de-duplicated constraint list.
115
+ # @return [Array<[Symbol, Object]>]
116
+ def drop_restated_null(cons)
117
+ nulls = cons.filter_map { |type, obj| obj[/\A(.+) == NULL\z/, 1] if type == :raw }
118
+ return cons if nulls.empty?
119
+
120
+ cons.reject do |type, obj|
121
+ type == :cmp && obj[1] == '==' && obj[2] == Conditional::ZERO && nulls.include?(obj[0])
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ # Record a descriptor the gadget closes on its way to the terminal call, by
128
+ # where it is read from. Which descriptor lands there decides whether the
129
+ # spawned shell keeps its I/O.
130
+ # @param [Object] fd The descriptor argument, as {#argument} returns it.
131
+ # @return [void]
132
+ # @example One the caller chooses is recorded; one fixed in the code is
133
+ # nobody's to change.
134
+ # note_closed_fd(Lambda.parse('[rsp+0x60]')) ; closed_fds #=> ['[rsp+0x60]']
135
+ # note_closed_fd(Lambda.parse('0x1')) ; closed_fds #=> []
136
+ def note_closed_fd(fd)
137
+ @closed_fds << fd.to_s unless fd.is_a?(Integer)
138
+ end
139
+
140
+ # Record what the callee does through a pointer argument.
141
+ # @return [Boolean] false to abort the candidate.
142
+ # @example Only a symbolic address carries a precondition a caller can
143
+ # arrange; a literal one is nobody's to make readable.
144
+ # record_pointer(Lambda.parse('rsp+0x40'), :deref) #=> true
145
+ # record_pointer(Lambda.parse('0x1'), :deref) #=> false
146
+ def record_pointer(arg, req)
147
+ return NULLABLE_REQUIREMENTS.include?(req) if arg.is_a?(Integer) && arg.zero?
148
+ return false unless arg.is_a?(OneGadget::Emulators::Lambda)
149
+
150
+ case req
151
+ when :writable then add_writable(arg)
152
+ when :deref then @deferred_reads << [arg, :readable]
153
+ when :nullable_deref then @deferred_reads << [arg, :nullable]
154
+ when :null then return require_null(arg)
155
+ end
156
+ true
157
+ end
158
+
159
+ # Record that +arg+ has to be NULL. An address that is mapped by the time
160
+ # the gadget runs -- the stack, a libc global -- names real memory and so
161
+ # can't also be NULL, and no caller can arrange otherwise.
162
+ # @return [Boolean] false to abort the candidate.
163
+ def require_null(arg)
164
+ return false if mapped_nonnull_pointer?(arg)
165
+
166
+ @constraints << [:raw, "#{arg} == NULL"]
167
+ true
168
+ end
169
+
170
+ # Now that emulation is complete and the full writable set is known, record
171
+ # the residual constraint for each deferred pointer argument, unless it is
172
+ # already known to reference mapped memory. A +:nullable+ deref becomes a
173
+ # +:raw+ +<arg> == NULL+ (take the skip-the-dereference path); a +:readable+
174
+ # deref becomes a +:readable+ constraint (NULL can't satisfy an unconditional
175
+ # dereference) -- a typed address constraint handled like +:writable+ (see
176
+ # {#constraints}). Idempotent: the queue is cleared once resolved.
177
+ def finalize_deferred_reads
178
+ @deferred_reads.each do |arg, kind|
179
+ if kind == :readable
180
+ next if mapped_nonnull_pointer?(arg) || writable_pointer?(arg)
181
+
182
+ @constraints << [:readable, arg]
183
+ else
184
+ next if deref_safe_pointer?(arg) || writable_pointer?(arg)
185
+
186
+ @constraints << [:raw, "#{arg} == NULL"]
187
+ end
188
+ end
189
+ @deferred_reads = []
190
+ end
191
+
192
+ # Whether dereferencing +val+ is safe: it is NULL, or a pointer already known
193
+ # to reference mapped memory (see {#mapped_pointer?}).
194
+ def deref_safe_pointer?(val)
195
+ return true if val.is_a?(Integer) && val.zero?
196
+ return false unless val.is_a?(OneGadget::Emulators::Lambda) && val.deref_count.zero?
197
+
198
+ mapped_pointer?(val.obj.to_s)
199
+ end
200
+
201
+ # Whether +val+ is already known to be a non-NULL pointer into mapped memory
202
+ # -- the safety bar for an *unconditional* dereference, which (unlike
203
+ # {#deref_safe_pointer?}) NULL cannot clear.
204
+ def mapped_nonnull_pointer?(val)
205
+ return false unless val.is_a?(OneGadget::Emulators::Lambda) && val.deref_count.zero?
206
+
207
+ mapped_pointer?(val.obj.to_s)
208
+ end
209
+
210
+ # Whether +val+ points into memory already known mapped from a store
211
+ # through its base during emulation -- either an explicit +writable+
212
+ # constraint (a store {#get_corresponding_stack} couldn't place, e.g. a
213
+ # compound destination), or memory tracked against that base (a store it
214
+ # could place -- the same evidence, a different bookkeeping path).
215
+ def writable_pointer?(val)
216
+ return false unless val.is_a?(OneGadget::Emulators::Lambda) && val.deref_count.zero?
217
+
218
+ base = val.obj.to_s
219
+ return true if @constraints.any? { |type, obj| type == :writable && obj.obj.to_s == base }
220
+
221
+ stack = get_corresponding_stack(base)
222
+ !!stack && !stack.empty?
223
+ end
224
+
225
+ # Whether an address expression names memory known to be mapped: a stack slot,
226
+ # the libc base, or a libc global.
227
+ def mapped_pointer?(obj)
228
+ obj.include?(sp) || obj == libc_base.obj.to_s || global_var?(obj)
229
+ end
230
+
231
+ # Record a "must be writable" constraint for a store's target address.
232
+ # @param [OneGadget::Emulators::Lambda] lmda The destination address, zero-deref
233
+ # (already +ref!+'d by the caller).
234
+ def add_writable(lmda)
235
+ @constraints << [:writable, lmda] if needs_writable?(lmda)
236
+ end
237
+
238
+ # Require +val+'s pointer be readable when a load dereferences an
239
+ # uncontrolled base -- one that doesn't root at mapped memory (see
240
+ # {#mapped_pointer?}), since a value read from the stack or a libc global is
241
+ # reliably valid. Deferred like a safe call's +:deref+ so a later store
242
+ # proving the base writable still discharges it.
243
+ # @param [Object] val The loaded value, as produced by {#arg_to_lambda}.
244
+ # @example note_read(arg_to_lambda('[x19+0xed8]')) records readable: x19+0xed8
245
+ def note_read(val)
246
+ return unless val.is_a?(OneGadget::Emulators::Lambda) && val.deref_count.positive?
247
+
248
+ ptr = val.dup.ref!
249
+ root = root_base(ptr)
250
+ return if root && mapped_pointer?(root.to_s)
251
+
252
+ @deferred_reads << [ptr, :readable]
253
+ end
254
+
255
+ # Whether a store through +lmda+ imposes a "must be writable" constraint. It
256
+ # lands on writable-or-fixed memory for free when the target is the stack
257
+ # pointer (the stack is always writable), the program counter, or the libc
258
+ # base (a fixed libc-internal address); a frame pointer or attacker register
259
+ # still needs the constraint.
260
+ # @example (sp is +rsp+, pc is +rip+)
261
+ # needs_writable?(arg_to_lambda('rax')) #=> true # an attacker register
262
+ # needs_writable?(arg_to_lambda('[rsp+0x8]')) #=> false # the stack is writable
263
+ # needs_writable?(arg_to_lambda('$base+0x10')) #=> false # a fixed libc global
264
+ def needs_writable?(lmda)
265
+ ![sp, pc, libc_base.obj.to_s].include?(lmda.obj.to_s)
266
+ end
267
+ end
268
+ end
269
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'one_gadget/emulators/lambda'
4
+
5
+ module OneGadget
6
+ module Emulators
7
+ # What an instruction leaves in a register: the architecture-independent half of
8
+ # an +inst_*+ handler. A result that stays an offset from a known base keeps that
9
+ # form, so the rest of the emulator can resolve it against tracked memory; one
10
+ # that does not is named as the operation it is, since a caller deriving a value
11
+ # that way still has to arrange its operands. Anything neither of those aborts
12
+ # the candidate rather than being recorded as a value it is not. Mixed into
13
+ # {Processor}.
14
+ module DataProcessing
15
+ private
16
+
17
+ # The value +op+ produces from +lhs+ and +rhs+: folded when both are
18
+ # concrete, and otherwise named as the operation itself, since no
19
+ # base+offset expresses it (see {Lambda.operation}). +nil+ when it is
20
+ # neither -- an operation on something this emulator cannot name, which the
21
+ # caller reports against its own mnemonic.
22
+ # @param [Symbol] op A Ruby operator that doubles as how the operation renders.
23
+ # @param [Lambda, Integer] lhs The value operated on.
24
+ # @param [Lambda, Integer] rhs The value it is operated on with.
25
+ # @return [Lambda, Integer, nil] The result, or nil when it is not one this
26
+ # emulator can name.
27
+ # @example (amd64) +and rax, 0xf+ with rax unknown leaves +(rax & 0xf)+
28
+ # operation_result(:&, registers['rax'], 0xf)
29
+ def operation_result(op, lhs, rhs)
30
+ return lhs.send(op, rhs) if lhs.is_a?(Integer) && rhs.is_a?(Integer)
31
+ return nil unless lhs.is_a?(OneGadget::Emulators::Lambda)
32
+
33
+ # Exclusive-or of a value with itself is zero whether or not the value is
34
+ # known -- how every arch spells "zero this register".
35
+ return 0 if op == :^ && lhs.to_s == rhs.to_s
36
+
37
+ OneGadget::Emulators::Lambda.operation(lhs, op.to_s, rhs)
38
+ end
39
+
40
+ # Add or subtract, and store the result.
41
+ # @example An offset from a known base stays one, which the rest of the
42
+ # emulator resolves against tracked memory; a sum of two unknowns is named
43
+ # as the operation it is, so a pointer derived that way still says what the
44
+ # caller has to arrange.
45
+ # arith(:+, 'rax', 'rsp', '0x10') ; registers['rax'] #=> rsp+0x10
46
+ # arith(:+, 'rax', 'rdi', 'rsi') ; registers['rax'] #=> (rdi + rsi)
47
+ # @param [Symbol] op +:++ or +:-+.
48
+ # @param [String] dst The destination register.
49
+ # @param [String] src The value added to, or the only operand given.
50
+ # @param [String, nil] op2 The value to add, or nil in the 2-operand form.
51
+ # @return [void]
52
+ # @raise [OneGadget::Error::UnsupportedInstructionArgumentError]
53
+ # When the result is not one this emulator can name.
54
+ def arith(op, dst, src, op2)
55
+ check_register!(dst)
56
+ src, op2 = shorthand(dst, src, op2)
57
+ lhs = value_of(src)
58
+ rhs = value_of(op2)
59
+
60
+ result = offset_result(op, lhs, rhs)
61
+ # The stack pointer has to stay an offset from itself: every tracked
62
+ # stack slot is keyed on it, and a candidate that reads one back after
63
+ # allocating a variable-size frame would be answered from the wrong
64
+ # place. Such a frame also puts the array a gadget builds at an address
65
+ # only a register the caller supplies decides, which no constraint this
66
+ # emulator emits states.
67
+ result ||= operation_result(op, lhs, rhs) unless dst == sp
68
+ raise_unsupported(op, dst, src, op2) if result.nil?
69
+
70
+ registers[dst] = result
71
+ end
72
+
73
+ # +lhs op rhs+ when the result is an offset from +lhs+'s base, which
74
+ # {Lambda} expresses directly. +nil+ when it is not, leaving the caller to
75
+ # name the operation instead.
76
+ # @return [Lambda, Integer, nil]
77
+ def offset_result(op, lhs, rhs)
78
+ return lhs.send(op, rhs) if rhs.is_a?(Integer)
79
+ # Adding a known offset to an unknown value is the same value shifted;
80
+ # subtracting from one is not, so only addition commutes here.
81
+ return rhs + lhs if op == :+ && lhs.is_a?(Integer)
82
+
83
+ nil
84
+ end
85
+
86
+ # Apply a data-processing instruction and store its result. An arch that
87
+ # allows the 2-operand shorthand may pass +src+ as the only operand.
88
+ # @param [Symbol] op A Ruby operator that doubles as how the operation renders.
89
+ # @param [String] dst The destination register.
90
+ # @param [String] src The left operand, or the only one given (see {#shorthand}).
91
+ # @param [String, nil] op2 The right operand, or nil in the 2-operand form.
92
+ # @param [String] name The mnemonic, named in an abort.
93
+ # @return [void]
94
+ # @raise [OneGadget::Error::UnsupportedInstructionArgumentError]
95
+ # When the result is nothing this emulator can name.
96
+ def data_op(op, dst, src, op2, name:)
97
+ check_register!(dst)
98
+ src, op2 = shorthand(dst, src, op2)
99
+ result = operation_result(op, value_of(src), value_of(op2))
100
+ raise_unsupported(name, dst, src, op2) if result.nil?
101
+
102
+ # A shift can push bits past the register width, which the arbitrary-
103
+ # precision fold above would otherwise keep.
104
+ registers[dst] = result.is_a?(Integer) ? result & width_mask : result
105
+ end
106
+
107
+ # +op2+ with every bit flipped. Only a concrete value has a complement this
108
+ # emulator can name; a symbolic one aborts rather than being recorded as a
109
+ # mask it isn't.
110
+ # @param [String] name The mnemonic to report an abort against.
111
+ # @param [String] op2 The operand to complement.
112
+ # @param [Array<String>] reported The operands to name in that abort.
113
+ # @return [Integer] +op2+ complemented, within the register width.
114
+ def complement(name, op2, *reported)
115
+ value = value_of(op2)
116
+ raise_unsupported(name, *reported) unless value.is_a?(Integer)
117
+
118
+ ~value & width_mask
119
+ end
120
+
121
+ # Every bit of a register, for masking a result back to its width.
122
+ # @return [Integer]
123
+ def width_mask = (1 << self.class.bits) - 1
124
+
125
+ # The value of an operand. {Arm} overrides it for +pc+, whose value depends
126
+ # on the address of the instruction reading it.
127
+ # @param [String] arg The operand, as written.
128
+ # @return [OneGadget::Emulators::Lambda, Integer] Its current value.
129
+ def value_of(arg) = arg_to_lambda(arg)
130
+
131
+ # Expand a 2-operand data-processing form into its (src, op2) operands:
132
+ # +add dst, op2+ is shorthand for +add dst, dst, op2+, while an explicit
133
+ # 3-operand form is passed through unchanged.
134
+ # @param [String] dst The destination register, which the 2-operand form
135
+ # also reads as its left operand.
136
+ # @param [String] src The left operand, or the right one in the 2-operand form.
137
+ # @param [String, nil] op2 The right operand, or nil in the 2-operand form.
138
+ # @return [(String, String)] The left and right operands.
139
+ # @example
140
+ # shorthand('r0', 'r4', nil) # 2-operand: add r0, r4
141
+ # #=> ['r0', 'r4']
142
+ # shorthand('r0', 'r4', '8') # 3-operand: add r0, r4, 8
143
+ # #=> ['r4', '8']
144
+ def shorthand(dst, src, op2)
145
+ op2.nil? ? [dst, src] : [src, op2]
146
+ end
147
+
148
+ # An instruction with no effect this emulator models anything of.
149
+ # @return [void]
150
+ def inst_nop(*); end
151
+
152
+ # Replace register tokens that currently hold a concrete integer with that
153
+ # integer, so a register-indexed memory operand becomes an offset one the
154
+ # Lambda parser handles.
155
+ # @example
156
+ # # with the index register currently holding 0xd8
157
+ # resolve_int_regs('[r8, r2]') #=> '[r8, 0xd8]'
158
+ # resolve_int_regs('[x8, x2]') #=> '[x8, 0xd8]'
159
+ def resolve_int_regs(str)
160
+ str.gsub(/[a-z]+\d*/) do |tok|
161
+ v = registers[tok] if register?(tok)
162
+ v.is_a?(Integer) ? OneGadget::Helper.hex(v) : tok
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -9,6 +9,7 @@ module OneGadget
9
9
  class I386 < X86
10
10
  class << self
11
11
  # Yap, bits.
12
+ # @return [Integer]
12
13
  def bits
13
14
  32
14
15
  end
@@ -19,12 +20,9 @@ module OneGadget
19
20
  super(OneGadget::ABI.i386, 'esp', 'ebp', 'eip')
20
21
  end
21
22
 
22
- # Get function call arguments.
23
- #
24
- # For i386 this is a little bit tricky.
25
- # We need to fetch the stack slots reference to current 'esp'
26
- # but not original 'esp'.
27
- # So we need to evaluate the offset of current esp first.
23
+ # The value on the stack slot holding the +idx+-th argument. The slots are
24
+ # relative to the +esp+ the line sees, not the one the candidate was entered
25
+ # with, so its offset is evaluated first.
28
26
  # @param [Integer] idx The 0-based index of the argument.
29
27
  # @return [Lambda, Integer] The value on the stack slot holding the +idx+-th argument.
30
28
  def argument(idx)
@@ -46,11 +46,34 @@ module OneGadget
46
46
  # @param [String] cmd One line of objdump output.
47
47
  # @return [Boolean] +true+ if +cmd+ contains this instruction's mnemonic.
48
48
  def match?(cmd)
49
- (cmd =~ /#{inst}\s/) != nil
49
+ cmd.match?(/#{Regexp.escape(inst)}\s/)
50
+ end
51
+
52
+ class << self
53
+ # The emulator method that runs +mnemonic+. A mnemonic is not always a
54
+ # method name -- one can carry a suffix spelled with a dot -- so the dots
55
+ # become underscores. Named here, rather than at each dispatch, so an
56
+ # emulator defining a family of handlers at once agrees with the dispatcher
57
+ # about what to call them.
58
+ # @param [String] mnemonic
59
+ # @return [Symbol]
60
+ # @example
61
+ # Instruction.handler_name('mov') #=> :inst_mov
62
+ # Instruction.handler_name('sext.w') #=> :inst_sext_w
63
+ def handler_name(mnemonic) = :"inst_#{mnemonic.tr('.', '_')}"
64
+ end
65
+
66
+ # The emulator method that runs this instruction.
67
+ # @return [Symbol]
68
+ # @see .handler_name
69
+ def handler
70
+ @handler ||= self.class.handler_name(inst)
50
71
  end
51
72
 
52
73
  private
53
74
 
75
+ # Split an operand list on the commas that separate operands, which are the
76
+ # ones outside brackets: a memory operand carries commas of its own.
54
77
  def parse_args(str)
55
78
  args = []
56
79
  cur = +''
@@ -12,7 +12,7 @@ module OneGadget
12
12
  # 4. dereferenced {Lambda}
13
13
  class Lambda
14
14
  attr_accessor :obj # @return [String, Lambda] The object currently related to.
15
- attr_accessor :immi # @return [Integer] The immidiate value currently added.
15
+ attr_accessor :immi # @return [Integer] The immediate value currently added.
16
16
  attr_accessor :deref_count # @return [Integer] The times of dereference.
17
17
  attr_accessor :op # @return [String, nil] The operator applied to {#obj}, for an operation (see {.operation}).
18
18
  attr_accessor :rhs # @return [Integer, Lambda, String, nil] The operator's right operand.
@@ -44,7 +44,7 @@ module OneGadget
44
44
  end
45
45
 
46
46
  # Implement subtract with +Numeric+.
47
- # @param [Numeric] other Value to substract.
47
+ # @param [Numeric] other Value to subtract.
48
48
  # @return [Lambda] The result.
49
49
  def -(other)
50
50
  self + -other
@@ -58,7 +58,7 @@ module OneGadget
58
58
 
59
59
  # Decrease dereference count by 1.
60
60
  # @return [self]
61
- # @raise [Error::InstrutionArgumentError] When this object cannot be referenced anymore.
61
+ # @raise [Error::InstructionArgumentError] When this object cannot be referenced anymore.
62
62
  def ref!
63
63
  raise Error::InstructionArgumentError, 'Cannot reference anymore!' if @deref_count <= 0
64
64
 
@@ -186,7 +186,9 @@ module OneGadget
186
186
 
187
187
  private
188
188
 
189
- # @return [(String, Integer)]
189
+ # Split a memory operand into what it is based on and how far from it.
190
+ # @param [String] arg The operand, with its brackets already removed.
191
+ # @return [(String, Integer)] The base, and the offset from it.
190
192
  def mem_obj(arg)
191
193
  # We have three forms:
192
194
  # 0. reg