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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +215 -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 +6 -4
- 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 +25 -3
- 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 +27 -37
- 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
|
@@ -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
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
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
|
-
(
|
|
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
|
|
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
|
|
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::
|
|
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
|
-
#
|
|
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
|