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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +197 -65
- data/README.md +96 -22
- data/lib/one_gadget/abi.rb +41 -5
- data/lib/one_gadget/builds/libc-2.31-93b46e0027747153e336c3fd9431ce9a5d82ad00.rb +563 -0
- data/lib/one_gadget/builds/libc-2.35-891c1403437a4e30e684e0c8e34b87a09e4298e5.rb +434 -0
- data/lib/one_gadget/builds/libc-2.39-a1d1cf4badf1f5dfe57ff1d17bd692ccc6fcd5c5.rb +531 -0
- data/lib/one_gadget/builds/libc-2.39-cd8f5a207dd67aea370d2b471a54c3e56f44ab18.rb +531 -0
- data/lib/one_gadget/builds/libc-2.43-b50ceafbd17dc6bceee344a66671c7eaa152bef4.rb +15 -0
- data/lib/one_gadget/emulators/aarch64.rb +5 -2
- data/lib/one_gadget/emulators/amd64.rb +1 -0
- data/lib/one_gadget/emulators/arm.rb +27 -18
- data/lib/one_gadget/emulators/arm_family.rb +35 -160
- data/lib/one_gadget/emulators/conditional.rb +28 -22
- data/lib/one_gadget/emulators/constraints.rb +269 -0
- data/lib/one_gadget/emulators/data_processing.rb +167 -0
- data/lib/one_gadget/emulators/i386.rb +4 -6
- data/lib/one_gadget/emulators/instruction.rb +24 -1
- data/lib/one_gadget/emulators/lambda.rb +3 -1
- data/lib/one_gadget/emulators/mips.rb +289 -0
- data/lib/one_gadget/emulators/processor.rb +33 -455
- data/lib/one_gadget/emulators/register_file.rb +19 -10
- data/lib/one_gadget/emulators/riscv64.rb +265 -0
- data/lib/one_gadget/emulators/safe_calls.rb +9 -3
- data/lib/one_gadget/emulators/tracked_memory.rb +209 -0
- data/lib/one_gadget/emulators/x86.rb +32 -22
- data/lib/one_gadget/fetchers/aarch64.rb +0 -27
- data/lib/one_gadget/fetchers/amd64.rb +0 -24
- data/lib/one_gadget/fetchers/argument_resolution.rb +340 -0
- data/lib/one_gadget/fetchers/arm.rb +99 -44
- data/lib/one_gadget/fetchers/base.rb +142 -640
- data/lib/one_gadget/fetchers/candidate_walk.rb +150 -0
- data/lib/one_gadget/fetchers/disassembly.rb +252 -0
- data/lib/one_gadget/fetchers/dynamic_symbols.rb +104 -0
- data/lib/one_gadget/fetchers/i386.rb +5 -8
- data/lib/one_gadget/fetchers/mips.rb +478 -0
- data/lib/one_gadget/fetchers/objdump.rb +24 -2
- data/lib/one_gadget/fetchers/riscv64.rb +78 -0
- data/lib/one_gadget/fetchers/x86.rb +19 -0
- data/lib/one_gadget/fetchers.rb +22 -6
- data/lib/one_gadget/gadget.rb +25 -34
- data/lib/one_gadget/helper.rb +19 -5
- data/lib/one_gadget/one_gadget.rb +6 -0
- data/lib/one_gadget/version.rb +1 -1
- data/lib/one_gadget.rb +1 -1
- metadata +18 -6
- data/lib/one_gadget/builds/libc-2.26-2104f3d4ad5cf68603afbe7ba1a17f5ac99c5988.rb +0 -227
- data/lib/one_gadget/builds/libc-2.26-ddcc13122ddbfe5e5ef77d4ebe66d124ae5762c2.rb +0 -300
- 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;
|
|
16
|
-
#
|
|
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
|
-
#
|
|
365
|
-
#
|
|
366
|
-
#
|
|
367
|
-
#
|
|
368
|
-
#
|
|
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,
|
|
410
|
-
#
|
|
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.
|
|
72
|
-
#
|
|
73
|
-
#
|
|
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
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
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
|
|