one_gadget 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +197 -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 +3 -1
  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 +24 -2
  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 +25 -34
  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
@@ -2,9 +2,12 @@
2
2
 
3
3
  require 'one_gadget/abi'
4
4
  require 'one_gadget/emulators/conditional'
5
+ require 'one_gadget/emulators/constraints'
6
+ require 'one_gadget/emulators/data_processing'
5
7
  require 'one_gadget/emulators/lambda'
6
8
  require 'one_gadget/emulators/register_file'
7
9
  require 'one_gadget/emulators/safe_calls'
10
+ require 'one_gadget/emulators/tracked_memory'
8
11
  require 'one_gadget/error'
9
12
 
10
13
  module OneGadget
@@ -12,24 +15,26 @@ module OneGadget
12
15
  module Emulators
13
16
  # Base of the per-architecture instruction emulators, used to symbolically
14
17
  # execute a candidate and solve its constraints. A subclass implements the
15
- # arch's supported instructions, calling convention and stack model; the
16
- # shared branch/compare machinery comes from {Conditional}.
18
+ # arch's supported instructions, calling convention and stack model; what it
19
+ # inherits here is the emulation lifecycle (parse, dispatch, and the calls a
20
+ # candidate may cross) plus four architecture-independent concerns kept in
21
+ # modules of their own: {Conditional} for the branch/compare machinery,
22
+ # {Constraints} for what a gadget requires of its caller, {TrackedMemory} for
23
+ # the memory a candidate reads and writes, and {DataProcessing} for what an
24
+ # instruction leaves in a register.
17
25
  #
18
26
  # To add an architecture, see +docs/adding-an-architecture.md+.
19
27
  class Processor
20
28
  include Conditional
29
+ include Constraints
30
+ include DataProcessing
31
+ include TrackedMemory
21
32
 
22
33
  attr_reader :registers # @return [RegisterFile] The current registers' state.
23
34
  attr_reader :sp # @return [String] Stack pointer.
24
35
  attr_reader :pc # @return [String] Program counter.
25
36
  attr_reader :bp # @return [String, nil] Frame pointer, or nil when this arch tracks none.
26
37
 
27
- # @return [Hash{Integer => OneGadget::Emulators::Lambda}] Memory written through +sp+.
28
- def sp_based_stack = get_corresponding_stack(sp)
29
-
30
- # @return [Hash{Integer => Lambda}, nil] Memory written through {#bp}, or nil when the arch has none.
31
- def bp_based_stack = bp && get_corresponding_stack(bp)
32
-
33
38
  # Instantiate a {Processor} object.
34
39
  # @param [Array<String>] registers
35
40
  # Registers that supported in the architecture.
@@ -47,15 +52,6 @@ module OneGadget
47
52
  @pending = nil # a conditional branch awaiting one-line-ahead resolution
48
53
  end
49
54
 
50
- # Enable frame-pointer stack tracking with +bp+ as the frame register, so a
51
- # gadget staging data at +[bp+imm]+ (e.g. an argv array off the frame
52
- # pointer) is recovered instead of collapsing to a bare +writable:+. A nil
53
- # +bp+ leaves the arch +sp+-only. Call from the arch initializer after +super+.
54
- # @return [void]
55
- def setup_frame_pointer(bp)
56
- @bp = bp
57
- end
58
-
59
55
  # Function names whose call ends a gadget: the real +exec*+ entry points.
60
56
  # Deliberately excludes the +posix_spawn+ setup helpers
61
57
  # (+posix_spawnattr_*+, +posix_spawn_file_actions_*+), which merely share the
@@ -75,7 +71,7 @@ module OneGadget
75
71
 
76
72
  # Record a reached terminal +exec*+ call as the gadget's effect and stop
77
73
  # emulating: it is the gadget's goal, and any following instruction would
78
- # clobber the argument registers that {#resolve} reads to describe it.
74
+ # clobber the argument registers that {OneGadget::Fetchers::ArgumentResolution#resolve} reads to describe it.
79
75
  # @param [String] addr The call target.
80
76
  # @return [Symbol] +:fail+, the sentinel {#process!} maps to "stop".
81
77
  def reach_terminal_call(addr)
@@ -172,225 +168,27 @@ module OneGadget
172
168
  def argument(_idx); raise NotImplementedError
173
169
  end
174
170
 
175
- # Marks a register holding whatever a call returned or left behind; see
176
- # {#clobber_caller_saved}.
177
- CLOBBERED = '$clobbered'
178
-
179
- # Constraint types whose payload is an address {Lambda} asserting the target
180
- # is mapped -- +:writable+ (a store target) and +:readable+ (an unconditional
181
- # dereference, see {#finalize_deferred_reads}). Both are keyed, offset-
182
- # normalised, and imply non-NULL identically; they differ only in how they
183
- # render (see {#render_constraint}). The remaining type, +:raw+, carries a
184
- # ready-made constraint string that keys on itself, and +:cmp+ a comparison
185
- # recorded as its +[lhs, operator, rhs]+ parts (see {Conditional}), so it can
186
- # be inspected rather than re-parsed from the rendered text.
187
- ADDRESS_TYPES = %i[writable readable].freeze
188
-
189
- # {SafeCalls} requirements naming what a callee does with a pointer argument,
190
- # each recorded as something the caller must arrange (see {#record_pointer}),
191
- # as opposed to a precondition read off the value as it stands.
192
- POINTER_REQUIREMENTS = %i[writable deref nullable_deref null].freeze
193
-
194
- # The {POINTER_REQUIREMENTS} a NULL argument already satisfies: both ask for
195
- # a pointer the callee will leave alone, and NULL is how that is asked for.
196
- NULLABLE_REQUIREMENTS = %i[nullable_deref null].freeze
197
-
198
- # @return [Array<String>] Where each descriptor this candidate closes is read
199
- # from, in the order they are closed, without repeats.
200
- def closed_fds
201
- @closed_fds.uniq
202
- end
203
-
204
- # @return [Array<String>]
205
- # Extra constraints found during execution.
206
- def constraints
207
- finalize_deferred_reads
208
- return [] if @constraints.empty?
209
-
210
- # An address constraint is keyed by its base register (deref-0) or full
211
- # expression (compound); several through one base (e.g. stores at reg+0x0
212
- # and reg+0x8) impose the same requirement, so keep just the smallest
213
- # offset (sort ascending, then uniq keeps that first).
214
- cons = @constraints.sort_by { |type, obj| address_deref0?(type, obj) ? obj.immi : 0 }
215
- .uniq { |type, obj| constraint_key(type, obj) }
216
- cons = drop_restated_null(drop_implied_nonzero(cons))
217
- cons.map { |type, obj| render_constraint(type, obj) }.sort
218
- end
219
-
220
- # Whether +(type, obj)+ is an address constraint on a bare (deref-0) target,
221
- # i.e. one carrying a base register and offset to normalise.
222
- def address_deref0?(type, obj)
223
- ADDRESS_TYPES.include?(type) && obj.deref_count.zero?
224
- end
225
-
226
- # De-duplication key: an address constraint collapses per (type, base) so
227
- # constraints of different types on the same register stay distinct; a raw
228
- # constraint keys on its own text.
229
- def constraint_key(type, obj)
230
- return obj unless ADDRESS_TYPES.include?(type)
231
-
232
- [type, obj.deref_count.zero? ? obj.obj.to_s : obj.to_s]
233
- end
234
-
235
- # Render a constraint to its output string.
236
- def render_constraint(type, obj)
237
- case type
238
- when :writable then "writable: #{obj}"
239
- when :readable then "readable: #{obj}"
240
- when :cmp then obj.join(' ')
241
- else obj
242
- end
243
- end
244
-
245
- # Drop a "<reg> != 0x0" branch constraint that another constraint already
246
- # implies: an address constraint (+writable: <reg>+imm+ store target, or
247
- # +readable: <reg>+) forces <reg> to be a valid (mapped, non-NULL) pointer,
248
- # so a NULL-check branch on the same register adds nothing. Keeps the
249
- # emitted set minimal.
250
- # @param [Array<[Symbol, Object]>] cons The de-duplicated constraint list.
251
- # @return [Array<[Symbol, Object]>]
252
- def drop_implied_nonzero(cons)
253
- nonzero_regs = cons.filter_map do |type, obj|
254
- obj.obj.to_s if address_deref0?(type, obj)
255
- end
256
- return cons if nonzero_regs.empty?
257
-
258
- cons.reject do |type, obj|
259
- type == :cmp && obj[1] == '!=' && obj[2] == ZERO && nonzero_regs.include?(obj[0])
260
- end
261
- end
262
-
263
- # Drop a "<X> == 0x0" branch constraint that a NULL requirement on the same
264
- # value already states (see {#require_null}). Both ask for the same zero, and
265
- # the one naming it NULL is the one that says what the zero is for.
266
- # @param [Array<[Symbol, Object]>] cons The de-duplicated constraint list.
267
- # @return [Array<[Symbol, Object]>]
268
- def drop_restated_null(cons)
269
- nulls = cons.filter_map { |type, obj| obj[/\A(.+) == NULL\z/, 1] if type == :raw }
270
- return cons if nulls.empty?
271
-
272
- cons.reject do |type, obj|
273
- type == :cmp && obj[1] == '==' && obj[2] == ZERO && nulls.include?(obj[0])
274
- end
275
- end
276
-
277
- # Where +address+ lands in the memory this emulator tracks: the stack it
278
- # falls in and its offset within it. A load or store passes the address it
279
- # dereferences, i.e. its operand with that dereference peeled off.
280
- # @param [Lambda, String] address An address.
281
- # @return [(Hash{Integer => Lambda}?, Integer)] The stack, +nil+ if none
282
- # tracks this address, and the offset to index it at.
283
- # @example an offset from a register
284
- # resolve_address(Lambda.parse('rsp+0x10')) #=> [sp_based_stack, 0x10]
285
- # @example an offset from a pointer no register names
286
- # resolve_address(Lambda.parse('[rbp-0x48]+0x8')) #=> [the "[rbp-0x48]" stack, 0x8]
287
- def resolve_address(address)
288
- base, offset = address_base(address)
289
- [get_corresponding_stack(base), offset]
290
- end
291
-
292
- # The memory +base+ addresses: what this candidate has written through it,
293
- # keyed by offset. Every base gets one -- the stack pointer, the frame
294
- # pointer, any other register, and a value no register names at all (a
295
- # pointer the candidate derived and then built an array through).
296
- #
297
- # Keyed by how the base renders, which is what makes one store enough: a
298
- # register that gets reassigned addresses somewhere else and renders
299
- # differently, so it lands on a different key without any invalidation to
300
- # arrange. Only a store overwriting what the base itself reads from would
301
- # break that, which a candidate short enough to be a gadget doesn't do.
302
- # @example a register
303
- # get_corresponding_stack('x21')
304
- # @example a pointer rounded down before use
305
- # get_corresponding_stack(Lambda.parse('(rsi & 0xfffffffffffffff0)'))
306
- # @param [String, Lambda] base A base, as {#resolve_address} yields it --
307
- # not an offset expression, whose offset belongs in the key it indexes.
308
- # @return [Hash{Integer => Lambda}, nil] nil when +base+ names nothing this
309
- # emulator tracks memory for.
310
- def get_corresponding_stack(base)
311
- return nil unless base.is_a?(OneGadget::Emulators::Lambda) || registers.key?(base.to_s)
312
-
313
- tracked_memory[base.to_s]
314
- end
315
-
316
171
  private
317
172
 
318
- # Every base this candidate has written through, each mapped to the memory
319
- # it addresses. See {#get_corresponding_stack}, which is how it is reached.
320
- # @return [Hash{String => Hash{Integer => Lambda}}]
321
- def tracked_memory
322
- @tracked_memory ||= Hash.new { |memory, base| memory[base] = tracked_stack(base) }
323
- end
324
-
325
- # Split +address+ into the base it is offset from and that offset, in the
326
- # forms {#get_corresponding_stack} and a tracked stack expect.
327
- # @param [Lambda, String] address See {#resolve_address}.
328
- # @return [(String, Lambda), Integer]
329
- def address_base(address)
330
- return [address, 0] unless address.is_a?(OneGadget::Emulators::Lambda)
331
- # Still a dereference deep, so the whole thing names one value rather than
332
- # an offset from anything: its own immediate is part of that name.
333
- return [address, 0] if address.deref_count.positive?
334
- # An operation's +obj+ is only the value it operates on, which addresses
335
- # somewhere else entirely -- a candidate building an array through
336
- # +(rsi & ~0xf)+ is not writing through +rsi+.
337
- return [address.dup.tap { |base| base.immi = 0 }, address.immi] if address.operation?
338
-
339
- [address.obj, address.immi]
340
- end
341
-
342
- # An always-on tracked stack keyed by offset: a Hash that lazily materialises
343
- # +[reg+off]+ as a one-deref {Lambda}. Used for the +sp+- and {#bp}-based stacks.
344
- def tracked_stack(reg)
345
- Hash.new do |h, k|
346
- h[k] = OneGadget::Emulators::Lambda.new(reg).tap do |lmda|
347
- lmda.immi = k
348
- lmda.deref!
349
- end
350
- end
351
- end
352
-
353
173
  def check_register!(reg)
354
174
  raise Error::InstructionArgumentError, "#{reg.inspect} is not a valid register" unless register?(reg)
355
175
  end
356
176
 
177
+ # Whether the +idx+-th argument meets +expect+, a requirement a catalogued
178
+ # call states about it that is not about the pointer being mapped (those go
179
+ # through {#record_pointer}). See {SafeCalls}.
357
180
  def check_argument(idx, expect)
358
181
  case expect
359
182
  when :global_var? then global_var?(argument(idx))
360
183
  end
361
184
  end
362
185
 
363
- # Accept a +call+ to a libc function the emulator treats as non-terminal
364
- # (a syscall wrapper). {SafeCalls::COMMON} maps each function name to its
365
- # per-argument requirements: an argument index paired with one of
366
- # * +:global_var?+ - a precondition that must already hold, else the
367
- # candidate is aborted (+:fail+).
368
- # * +:closed_fd+ - the descriptor the callee closes. Nothing is required of
369
- # it; it is recorded, because closing a standard descriptor changes what
370
- # the spawned shell can still do (see {#note_closed_fd}).
371
- # * +:null+ - the callee must be given NULL here, so +<arg> == NULL+ is
372
- # recorded for the caller to arrange. A value that can never be NULL --
373
- # a fixed address, or any other non-zero literal -- aborts the candidate.
374
- # @example +__sigaction(sig, act, oldact)+ writes the old action through
375
- # +oldact+ unless it is NULL
376
- # * +:nullable_deref+ - the callee dereferences this argument *unless it is
377
- # NULL*, which glibc guards with an explicit NULL check. When the pointer
378
- # isn't already known to be mapped, +<arg> == NULL+ is recorded so the
379
- # callee takes the skip-the-dereference path. Only tag an argument this way
380
- # after confirming the callee both NULL-checks it and still reaches the
381
- # terminal call on the NULL path.
382
- # * +:deref+ - the callee dereferences this argument *unconditionally* (no
383
- # NULL guard), so it can't be made safe by forcing it NULL. When the pointer
384
- # isn't already known to be mapped, +readable: <arg>+ is recorded so the
385
- # attacker knows it must reference readable memory.
386
- # @example +posix_spawnattr_setsigmask(attr, set)+ runs +attr->__ss = *set+
387
- # * +:writable+ - the callee *writes through* this argument (an out-param), so
388
- # +writable: <arg>+ is recorded (via {#add_writable}, which drops the
389
- # pc/+$base+/sp targets that are writable or fixed for free).
390
- # @example +posix_spawnattr_init(attr)+ writes +*attr+
391
- # Both deref checks are deferred to {#finalize_deferred_reads} because a
392
- # +<reg>+<imm>+ pointer may only become known-mapped once a later store marks
393
- # +<reg>+ writable.
186
+ # Accept a +call+ to a libc function the emulator treats as non-terminal (a
187
+ # syscall wrapper), applying whatever {SafeCalls::COMMON} records about its
188
+ # arguments. Both deref requirements are deferred to
189
+ # {#finalize_deferred_reads}, because a +<reg>+<imm>+ pointer may only become
190
+ # known-mapped once a later store marks +<reg>+ writable.
191
+ # @param [String] addr The call target, as objdump names it.
394
192
  # @return [nil, :fail] +nil+ = call accepted, +:fail+ = abort the candidate.
395
193
  def dispatch_safe_call(addr)
396
194
  func = SafeCalls::COMMON.keys.find { |n| addr.include?(n) }
@@ -406,23 +204,14 @@ module OneGadget
406
204
  nil
407
205
  end
408
206
 
409
- # Forget the caller-saved registers, which the call it just accepted is free
410
- # to leave in any state (see {OneGadget::ABI::CALLER_SAVED}). Keeping their
411
- # entry values would let a later branch on one -- +call __close+ then
412
- # +test eax, eax+ -- read as a condition on a value the caller chooses, when
413
- # it is really the callee's return.
414
- #
415
- # The return register is set to zero rather than forgotten: these calls are
416
- # accepted on the basis that they succeed, and success is what they all
417
- # report that way, so a branch on the result resolves instead of ending the
418
- # path. A path that needs the failing side then contradicts itself and drops.
419
- #
420
- # TODO: success is assumed, not required. A path that keeps going after the
421
- # call fails can reach a terminal call just as well, and is dropped here
422
- # only because the return is pinned. Modelling the result per function --
423
- # which values it can return, and what each one requires -- would let both
424
- # sides be walked, at the cost of a constraint describing the failing one.
207
+ # Forget the caller-saved registers, setting the return register to zero
208
+ # rather than forgetting it (see {OneGadget::ABI::CALLER_SAVED}).
425
209
  # @return [void]
210
+ # @example A later branch on the result resolves, rather than reading as a
211
+ # condition on a value the caller chooses.
212
+ # process('e6570: call 94180 <__close>')
213
+ # registers['rax'] #=> 0
214
+ # registers['rcx'] #=> $clobbered
426
215
  def clobber_caller_saved
427
216
  caller_saved.each do |reg|
428
217
  next unless registers.key?(reg)
@@ -463,112 +252,6 @@ module OneGadget
463
252
  value.is_a?(OneGadget::Emulators::Lambda) && value.obj == CLOBBERED
464
253
  end
465
254
 
466
- # Record a descriptor the gadget closes on its way to the terminal call, by
467
- # where it is read from.
468
- #
469
- # Only one the caller chooses is worth recording, since which descriptor
470
- # lands there decides whether the spawned shell keeps its I/O. One fixed in
471
- # the code is nobody's to change, and no path that reaches a terminal call
472
- # closes one, so it isn't modelled.
473
- # @param [Object] fd The descriptor argument, as {#argument} returns it.
474
- # @return [void]
475
- def note_closed_fd(fd)
476
- @closed_fds << fd.to_s unless fd.is_a?(Integer)
477
- end
478
-
479
- # Record what the callee does through a pointer argument.
480
- #
481
- # Only a symbolic value carries a precondition the caller can arrange: an
482
- # address that arrived as a literal is either one nobody can make readable
483
- # or writable, or NULL. The exception is the argument a callee leaves alone
484
- # when it is NULL -- passing NULL is exactly how that is asked for, so it is
485
- # accepted and needs nothing of the caller.
486
- # @return [Boolean] false to abort the candidate.
487
- def record_pointer(arg, req)
488
- return NULLABLE_REQUIREMENTS.include?(req) if arg.is_a?(Integer) && arg.zero?
489
- return false unless arg.is_a?(OneGadget::Emulators::Lambda)
490
-
491
- case req
492
- when :writable then add_writable(arg)
493
- when :deref then @deferred_reads << [arg, :readable]
494
- when :nullable_deref then @deferred_reads << [arg, :nullable]
495
- when :null then return require_null(arg)
496
- end
497
- true
498
- end
499
-
500
- # Record that +arg+ has to be NULL. An address that is mapped by the time
501
- # the gadget runs -- the stack, a libc global -- names real memory and so
502
- # can't also be NULL, and no caller can arrange otherwise.
503
- # @return [Boolean] false to abort the candidate.
504
- def require_null(arg)
505
- return false if mapped_nonnull_pointer?(arg)
506
-
507
- @constraints << [:raw, "#{arg} == NULL"]
508
- true
509
- end
510
-
511
- # Now that emulation is complete and the full writable set is known, record
512
- # the residual constraint for each deferred pointer argument, unless it is
513
- # already known to reference mapped memory. A +:nullable+ deref becomes a
514
- # +:raw+ +<arg> == NULL+ (take the skip-the-dereference path); a +:readable+
515
- # deref becomes a +:readable+ constraint (NULL can't satisfy an unconditional
516
- # dereference) -- a typed address constraint handled like +:writable+ (see
517
- # {#constraints}). Idempotent: the queue is cleared once resolved.
518
- def finalize_deferred_reads
519
- @deferred_reads.each do |arg, kind|
520
- if kind == :readable
521
- next if mapped_nonnull_pointer?(arg) || writable_pointer?(arg)
522
-
523
- @constraints << [:readable, arg]
524
- else
525
- next if deref_safe_pointer?(arg) || writable_pointer?(arg)
526
-
527
- @constraints << [:raw, "#{arg} == NULL"]
528
- end
529
- end
530
- @deferred_reads = []
531
- end
532
-
533
- # Whether dereferencing +val+ is safe: it is NULL, or a pointer already known
534
- # to reference mapped memory (see {#mapped_pointer?}).
535
- def deref_safe_pointer?(val)
536
- return true if val.is_a?(Integer) && val.zero?
537
- return false unless val.is_a?(OneGadget::Emulators::Lambda) && val.deref_count.zero?
538
-
539
- mapped_pointer?(val.obj.to_s)
540
- end
541
-
542
- # Whether +val+ is already known to be a non-NULL pointer into mapped memory
543
- # -- the safety bar for an *unconditional* dereference, which (unlike
544
- # {#deref_safe_pointer?}) NULL cannot clear.
545
- def mapped_nonnull_pointer?(val)
546
- return false unless val.is_a?(OneGadget::Emulators::Lambda) && val.deref_count.zero?
547
-
548
- mapped_pointer?(val.obj.to_s)
549
- end
550
-
551
- # Whether +val+ points into memory already known mapped from a store
552
- # through its base during emulation -- either an explicit +writable+
553
- # constraint (a store {#get_corresponding_stack} couldn't place, e.g. a
554
- # compound destination), or memory tracked against that base (a store it
555
- # could place -- the same evidence, a different bookkeeping path).
556
- def writable_pointer?(val)
557
- return false unless val.is_a?(OneGadget::Emulators::Lambda) && val.deref_count.zero?
558
-
559
- base = val.obj.to_s
560
- return true if @constraints.any? { |type, obj| type == :writable && obj.obj.to_s == base }
561
-
562
- stack = get_corresponding_stack(base)
563
- !!stack && !stack.empty?
564
- end
565
-
566
- # Whether an address expression names memory known to be mapped: a stack slot,
567
- # the libc base, or a libc global.
568
- def mapped_pointer?(obj)
569
- obj.include?(sp) || obj == libc_base.obj.to_s || global_var?(obj)
570
- end
571
-
572
255
  # The libc load base as a symbolic +$base+ lambda. Only the arches that
573
256
  # concretize libc-relative operands (amd64's +rip+, arm's +pc+) ever produce
574
257
  # it; elsewhere it never matches a real operand, so it's harmless.
@@ -578,86 +261,6 @@ module OneGadget
578
261
  @libc_base ||= OneGadget::Emulators::Lambda.new('$base')
579
262
  end
580
263
 
581
- # Record a "must be writable" constraint for a store's target address.
582
- # @param [OneGadget::Emulators::Lambda] lmda The destination address, zero-deref
583
- # (already +ref!+'d by the caller).
584
- def add_writable(lmda)
585
- @constraints << [:writable, lmda] if needs_writable?(lmda)
586
- end
587
-
588
- # The value an instruction reads through +val+: what this candidate put at
589
- # that address, when it is one the candidate has written, and the
590
- # dereference itself otherwise (recording the read, see {#note_read}).
591
- #
592
- # A slot the gadget fills in reads back as what was put there, so a
593
- # constraint on it names the value the caller has to arrange rather than
594
- # whatever the slot held on entry -- which the gadget has already replaced.
595
- # @param [Object] val The operand's value, as produced by {#arg_to_lambda}.
596
- # @return [Object]
597
- # @example (arm) +str r3, [sp, #4]+ then +ldr r0, [sp, #4]+ reads back r3
598
- def read_value(val)
599
- stored = stored_value(val)
600
- return stored unless stored.nil?
601
-
602
- note_read(val)
603
- val
604
- end
605
-
606
- # What this candidate stored at the address +val+ dereferences, or +nil+ if
607
- # it stored nothing there. Only a single dereference of an address this
608
- # emulator tracks names a slot it can answer for (see {#resolve_address}).
609
- # @param [Object] val
610
- # @return [Object, nil]
611
- def stored_value(val)
612
- return nil unless val.is_a?(OneGadget::Emulators::Lambda) && val.deref_count.positive?
613
-
614
- stack, offset = resolve_address(val.dup.ref!)
615
- stack&.key?(offset) ? stack[offset] : nil
616
- end
617
-
618
- # Require +val+'s pointer be readable when a load dereferences an
619
- # uncontrolled base -- one that doesn't root at mapped memory (see
620
- # {#mapped_pointer?}), since a value read from the stack or a libc global is
621
- # reliably valid. Deferred like a safe call's +:deref+ so a later store
622
- # proving the base writable still discharges it.
623
- # @param [Object] val The loaded value, as produced by {#arg_to_lambda}.
624
- # @example note_read(arg_to_lambda('[x19+0xed8]')) records readable: x19+0xed8
625
- def note_read(val)
626
- return unless val.is_a?(OneGadget::Emulators::Lambda) && val.deref_count.positive?
627
-
628
- ptr = val.dup.ref!
629
- root = root_base(ptr)
630
- return if root && mapped_pointer?(root.to_s)
631
-
632
- @deferred_reads << [ptr, :readable]
633
- end
634
-
635
- # The innermost base name of a (possibly nested or dereferenced) address
636
- # lambda, following +obj+ through any nested lambdas.
637
- # @param [OneGadget::Emulators::Lambda] lmda
638
- # @return [String, nil] The root base name, or +nil+ for an absolute address.
639
- # @example
640
- # root_base(arg_to_lambda('[[$base+0x10]+0x8]')) #=> '$base'
641
- # root_base(arg_to_lambda('x19+0xed8')) #=> 'x19'
642
- def root_base(lmda)
643
- obj = lmda.obj
644
- obj = obj.obj while obj.is_a?(OneGadget::Emulators::Lambda)
645
- obj
646
- end
647
-
648
- # Whether a store through +lmda+ imposes a "must be writable" constraint. It
649
- # lands on writable-or-fixed memory for free when the target is the stack
650
- # pointer (the stack is always writable), the program counter, or the libc
651
- # base (a fixed libc-internal address); a frame pointer or attacker register
652
- # still needs the constraint.
653
- # @example (sp is +rsp+, pc is +rip+)
654
- # needs_writable?(arg_to_lambda('rax')) #=> true # an attacker register
655
- # needs_writable?(arg_to_lambda('[rsp+0x8]')) #=> false # the stack is writable
656
- # needs_writable?(arg_to_lambda('$base+0x10')) #=> false # a fixed libc global
657
- def needs_writable?(lmda)
658
- ![sp, pc, libc_base.obj.to_s].include?(lmda.obj.to_s)
659
- end
660
-
661
264
  def register?(reg)
662
265
  registers.include?(reg)
663
266
  end
@@ -674,42 +277,17 @@ module OneGadget
674
277
  OneGadget::Emulators::Lambda.parse(arg, predefined: registers)
675
278
  end
676
279
 
677
- # The value +op+ produces from +lhs+ and +rhs+: folded when both are
678
- # concrete, and otherwise named as the operation itself, since no
679
- # base+offset expresses it (see {Lambda.operation}). +nil+ when it is
680
- # neither -- an operation on something this emulator cannot name, which the
681
- # caller reports against its own mnemonic.
682
- # @param [Symbol] op A Ruby operator that doubles as how the operation renders.
683
- # @param [Lambda, Integer] lhs The value operated on.
684
- # @param [Lambda, Integer] rhs The value it is operated on with.
685
- # @return [Lambda, Integer, nil] The result, or nil when it is not one this
686
- # emulator can name.
687
- # @example (amd64) +and rax, 0xf+ with rax unknown leaves +(rax & 0xf)+
688
- # operation_result(:&, registers['rax'], 0xf)
689
- def operation_result(op, lhs, rhs)
690
- return lhs.send(op, rhs) if lhs.is_a?(Integer) && rhs.is_a?(Integer)
691
- return nil unless lhs.is_a?(OneGadget::Emulators::Lambda)
692
-
693
- # Exclusive-or of a value with itself is zero whether or not the value is
694
- # known -- how every arch spells "zero this register".
695
- return 0 if op == :^ && lhs.to_s == rhs.to_s
696
-
697
- OneGadget::Emulators::Lambda.operation(lhs, op.to_s, rhs)
698
- end
699
-
700
280
  def raise_unsupported(inst, *args)
701
281
  raise OneGadget::Error::UnsupportedInstructionArgumentError, "#{inst} #{args.join(', ')}"
702
282
  end
703
283
 
704
- # Resolve +sp+- and (when tracked) {#bp}-relative operands to their offset.
705
- def eval_dict
706
- bp ? { sp => 0, bp => 0 } : { sp => 0 }
707
- end
708
-
709
284
  def size_t
710
285
  self.class.bits / 8
711
286
  end
712
287
 
288
+ # Whether +obj+ is reached through the program counter, which is what a
289
+ # libc global looks like once resolved -- as opposed to a value the caller
290
+ # supplies.
713
291
  def global_var?(obj)
714
292
  obj.to_s.include?(pc)
715
293
  end
@@ -36,15 +36,22 @@ module OneGadget
36
36
  @entry_halves = {}
37
37
  end
38
38
 
39
+ # @param [String] name Any name the architecture accepts, narrow or full.
40
+ # @return [OneGadget::Emulators::Lambda, Integer] What that name currently reads as.
39
41
  def [](name)
40
42
  value = super(full(name))
41
43
  narrow?(name) ? narrowed(name, value) : value
42
44
  end
43
45
 
46
+ # @param [String] name Any name the architecture accepts, narrow or full.
47
+ # @param [OneGadget::Emulators::Lambda, Integer] value
48
+ # @return [void]
44
49
  def []=(name, value)
45
50
  super(full(name), value)
46
51
  end
47
52
 
53
+ # @param [String] name Any name the architecture accepts, narrow or full.
54
+ # @return [Boolean] Whether its storage holds a value.
48
55
  def key?(name)
49
56
  super(full(name))
50
57
  end
@@ -68,20 +75,22 @@ module OneGadget
68
75
  @narrow_views.key?(name)
69
76
  end
70
77
 
71
- # The part of +value+ that +name+ addresses. Only one such part can be named
72
- # exactly: the low half of a register still holding what the gadget was
73
- # entered with, which is what the narrower name means. Anything else is
74
- # handed back whole, since no expression names half of it -- an
75
- # over-approximation, and one that only ever tightens a constraint or drops
76
- # the path it renders.
78
+ # The part of +value+ that +name+ addresses. Handing back the whole of what
79
+ # cannot be halved over-approximates, which only ever tightens a constraint
80
+ # or drops the path it renders.
77
81
  #
78
- # The name's own lambda is kept rather than rebuilt, so that repeated reads
79
- # of an untouched register return one object: a register's value is compared
80
- # by identity to tell a reassignment from the value that was there before
81
- # (see {Processor#reg_based_stack}).
82
+ # The name's own lambda is kept rather than rebuilt, so repeated reads of an
83
+ # untouched register return one object -- a value is compared by identity to
84
+ # tell a reassignment from what was there before (see
85
+ # {Processor#reg_based_stack}).
82
86
  # @param [String] name A narrower name (see {#narrow?}).
83
87
  # @param [Object] value The value held by the register it names part of.
84
88
  # @return [Object]
89
+ # @example (amd64) The low half of a register still holding what the gadget
90
+ # was entered with is the one part a name addresses exactly; no expression
91
+ # names half of anything else.
92
+ # narrowed('eax', Lambda.parse('rax')) #=> eax
93
+ # narrowed('eax', Lambda.parse('rsp+0x10')) #=> rsp+0x10
85
94
  def narrowed(name, value)
86
95
  return value unless entry_value?(value, full(name))
87
96