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
@@ -0,0 +1,265 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'one_gadget/abi'
4
+ require 'one_gadget/emulators/instruction'
5
+ require 'one_gadget/emulators/processor'
6
+
7
+ module OneGadget
8
+ module Emulators
9
+ # Emulator of RISC-V (RV64).
10
+ class Riscv64 < Processor
11
+ # Instantiate a {Riscv64} object.
12
+ def initialize
13
+ super(OneGadget::ABI.riscv64, 'sp')
14
+ @registers['zero'] = 0 # hardwired
15
+ @pc = 'pc'
16
+ setup_frame_pointer('s0') # track argv/data staged off the frame pointer
17
+ end
18
+
19
+ # @see OneGadget::Emulators::X86#process!
20
+ # @param [String] cmd One line from result of objdump.
21
+ # @return [Boolean] If successfully processed.
22
+ def process!(cmd)
23
+ resolve_pending_branch(cmd)
24
+ @cur_addr = cmd[/\A\s*([0-9a-f]+):/, 1]&.to_i(16)
25
+
26
+ mnem = mnemonic(cmd)
27
+ return handle_branch(mnem, cmd) != :fail if branch_mnem?(mnem)
28
+
29
+ inst, args = parse(cmd)
30
+ __send__(inst.handler, *args) != :fail
31
+ end
32
+
33
+ # A conditional branch of this arch compares its operands itself -- there is
34
+ # no flag register and no compare instruction -- so each mnemonic names the
35
+ # relation the operands must stand in for the branch to be taken. The
36
+ # assembler's spellings against zero (+beqz+, +blez+, ...) name one register
37
+ # and the reversed ones (+bgt+, +ble+, ...) read a relation the other way
38
+ # round; each is listed as the relation it states, so a constraint reads as
39
+ # the comparison was written.
40
+ COND = {
41
+ 'beq' => :eq, 'bne' => :ne,
42
+ 'blt' => :slt, 'bge' => :sge, 'bltu' => :ult, 'bgeu' => :uge,
43
+ 'bgt' => :sgt, 'ble' => :sle, 'bgtu' => :ugt, 'bleu' => :ule,
44
+ 'beqz' => :eq, 'bnez' => :ne,
45
+ 'bltz' => :slt, 'bgez' => :sge, 'blez' => :sle, 'bgtz' => :sgt
46
+ }.freeze
47
+
48
+ # The data-processing instructions, mapped to the Ruby operator that folds
49
+ # them and renders them alike. An immediate form differs from its register
50
+ # one only in how the right operand is written, which the operand reader
51
+ # already handles, so both name the same operator. Arithmetic shift right is
52
+ # absent: a value is held masked to its width, where +>>+ is the logical
53
+ # shift, and naming the arithmetic one as that would state a different
54
+ # instruction.
55
+ DATA_OPS = {
56
+ 'and' => :&, 'andi' => :&,
57
+ 'or' => :|, 'ori' => :|,
58
+ 'xor' => :^, 'xori' => :^,
59
+ 'sll' => :<<, 'slli' => :<<,
60
+ 'srl' => :>>, 'srli' => :>>
61
+ }.freeze
62
+ private_constant :DATA_OPS
63
+
64
+ # The loads, mapped to how many bytes each reads.
65
+ LOADS = { 'ld' => 8, 'lw' => 4, 'lwu' => 4, 'lh' => 2, 'lhu' => 2, 'lb' => 1, 'lbu' => 1 }.freeze
66
+ private_constant :LOADS
67
+
68
+ # The stores, mapped to how many bytes each writes.
69
+ STORES = { 'sd' => 8, 'sw' => 4, 'sh' => 2, 'sb' => 1 }.freeze
70
+ private_constant :STORES
71
+
72
+ # Supported instruction set. Anything not listed aborts the candidate.
73
+ # @return [Array<Instruction>] The supported instructions.
74
+ def instructions
75
+ [
76
+ Instruction.new('add', 3),
77
+ Instruction.new('addi', 3),
78
+ Instruction.new('auipc', 2),
79
+ Instruction.new('jal', 1..2),
80
+ Instruction.new('li', 2),
81
+ Instruction.new('lui', 2),
82
+ Instruction.new('mv', 2),
83
+ Instruction.new('nop', 0),
84
+ Instruction.new('sub', 3),
85
+ Instruction.new('not', 2)
86
+ ] + (LOADS.keys + STORES.keys).map { |mnem| Instruction.new(mnem, 2) } +
87
+ DATA_OPS.keys.map { |mnem| Instruction.new(mnem, 3) }
88
+ end
89
+
90
+ # Return the argument value of calling a function.
91
+ # @param [Integer] idx The 0-based index of the argument.
92
+ # @return [Lambda, Integer] The value held in register +a<idx>+, used for the +idx+-th argument.
93
+ def argument(idx)
94
+ registers["a#{idx}"]
95
+ end
96
+
97
+ private
98
+
99
+ def branch_mnem?(mnem)
100
+ mnem == 'j' || COND.key?(mnem)
101
+ end
102
+
103
+ # Operands of +cmd+ (mnemonic dropped), each stripped of a trailing +<symbol>+.
104
+ def operands(cmd)
105
+ cmd.sub(/\A[0-9a-f]+:\s*\S+\s*/, '').split(',').map { |o| o.strip.sub(/\s*<.*>\z/, '') }
106
+ end
107
+
108
+ # Record the comparison and decide the branch together, since one
109
+ # instruction is both (see {COND}). The zero spellings leave their second
110
+ # operand out; +zero+ names it, and reads as the +0x0+ it holds.
111
+ def handle_branch(mnem, cmd)
112
+ return true if mnem == 'j' # unconditional: control handled by the stitched path
113
+
114
+ ops = operands(cmd)
115
+ lhs, rhs = mnem.end_with?('z') ? [ops[0], 'zero'] : ops[0..1]
116
+ record_compare(:sub, operand_str(lhs), operand_str(rhs))
117
+ branch_on_compare(COND[mnem], ops.last.to_i(16))
118
+ end
119
+
120
+ # A direct call: record the terminal +exec*+ target, accept a known-safe
121
+ # syscall wrapper, or +:fail+ to abort the candidate. The two-operand form
122
+ # names the register the return address is written to, which is +ra+ for a
123
+ # call and is not modelled either way.
124
+ def inst_jal(*args)
125
+ addr = args.last
126
+ return reach_terminal_call(addr) if terminal_call?(addr)
127
+
128
+ dispatch_safe_call(addr)
129
+ end
130
+
131
+ # +auipc dst, imm+ is how this arch names an address relative to the
132
+ # instruction itself: the immediate objdump prints is the upper 20 bits, so
133
+ # the value is +pc + (imm << 12)+. The +addi+/load that follows applies the
134
+ # low half, which is what makes the pair resolve to the +$base+<off>+ form
135
+ # the rest of the engine reads as a libc global.
136
+ # @example the pair objdump resolves to 171fc0 in its own comment
137
+ # 9f48e: auipc a5,0xd3 # a5 = $base+0x17248e
138
+ # 9f492: ld a5,-1230(a5) # a5 = [$base+0x171fc0]
139
+ def inst_auipc(dst, imm)
140
+ check_register!(dst)
141
+
142
+ registers[dst] = pc_value + (imm.to_i(16) << 12)
143
+ end
144
+
145
+ # +lui dst, imm+ loads the same upper 20 bits, but as a plain value rather
146
+ # than an address, so nothing about the instruction's own location enters it.
147
+ def inst_lui(dst, imm)
148
+ check_register!(dst)
149
+
150
+ registers[dst] = (imm.to_i(16) << 12) & width_mask
151
+ end
152
+
153
+ # +li+ is the assembler's spelling of loading a constant, whatever the
154
+ # instructions it expands to.
155
+ def inst_li(dst, imm)
156
+ check_register!(dst)
157
+
158
+ registers[dst] = Integer(imm)
159
+ end
160
+
161
+ # This arch spells no modifier on an operand and has no 2-operand shorthand,
162
+ # so the arithmetic is the plain three-operand form; +addi+ differs from
163
+ # +add+ only in that its right operand is written as a literal.
164
+ def inst_add(dst, src, op2) = arith(:+, dst, src, op2)
165
+ alias inst_addi inst_add
166
+
167
+ # +sub dst, src, op2+. See {#inst_add}.
168
+ def inst_sub(dst, src, op2) = arith(:-, dst, src, op2)
169
+
170
+ def inst_mv(dst, src)
171
+ check_register!(dst)
172
+
173
+ registers[dst] = arg_to_lambda(src)
174
+ end
175
+
176
+ # Each data-processing mnemonic handled the one way, since they differ only
177
+ # in the operator applied (see {Processor#data_op} for the operands).
178
+ DATA_OPS.each do |mnem, op|
179
+ define_method(Instruction.handler_name(mnem)) do |dst, src, op2|
180
+ data_op(op, dst, src, op2, name: mnem)
181
+ end
182
+ end
183
+
184
+ # +not dst, src+ is every bit of +src+ flipped. Only a concrete value has a
185
+ # complement this emulator can name ({Processor#complement}); a symbolic one
186
+ # aborts rather than being recorded as a mask it isn't.
187
+ def inst_not(dst, src)
188
+ check_register!(dst)
189
+
190
+ registers[dst] = complement('not', src, dst, src)
191
+ end
192
+
193
+ # Each load and store handled the one way, since within a family they differ
194
+ # only in the width they touch (see {#load_value} and {#store_value}).
195
+ LOADS.each do |mnem, size|
196
+ define_method(Instruction.handler_name(mnem)) { |dst, mem| load_value(dst, mem, size) }
197
+ end
198
+ STORES.each do |mnem, size|
199
+ define_method(Instruction.handler_name(mnem)) { |src, mem| store_value(src, mem, size) }
200
+ end
201
+
202
+ # A load. The address is read like any other, so what the caller has to
203
+ # arrange about it is recorded the same way. A load narrower than a register
204
+ # only takes part of the word this emulator tracks, which is not a value it
205
+ # can name, so the register then holds what a call would have left: a path
206
+ # that goes on to depend on it is abandoned rather than described wrongly.
207
+ # @param [String] dst The destination register.
208
+ # @param [String] mem The memory operand, as written.
209
+ # @param [Integer] size How many bytes the load reads.
210
+ # @return [void]
211
+ def load_value(dst, mem, size)
212
+ check_register!(dst)
213
+
214
+ value = read_value(arg_to_lambda(mem_operand(mem)))
215
+ registers[dst] = size == size_t ? value : clobbered_value
216
+ end
217
+
218
+ # A store, tracked so a later load of the same slot reads it back, and
219
+ # requiring its target writable ({Processor#track_write}). A store narrower
220
+ # than a register leaves the rest of the slot holding whatever was there, so
221
+ # what the slot then holds is named as unknown rather than as the value
222
+ # stored -- the requirement that it be writable is what the candidate really
223
+ # establishes, and that is kept.
224
+ # @param [String] src The register holding the value stored.
225
+ # @param [String] mem The memory operand, as written.
226
+ # @param [Integer] size How many bytes the store writes.
227
+ # @return [void]
228
+ def store_value(src, mem, size)
229
+ check_register!(src)
230
+
231
+ dst_l = arg_to_lambda(mem_operand(mem)).ref!
232
+ track_write(dst_l, size == size_t ? registers[src] : clobbered_value)
233
+ end
234
+
235
+ # This arch writes a memory operand as an offset applied to one register,
236
+ # which the {Lambda} parser reads in its bracketed form. The offset is
237
+ # decimal, as objdump prints every operand but an upper immediate.
238
+ # @param [String] mem The operand, as written.
239
+ # @return [String]
240
+ # @example
241
+ # mem_operand('-1230(a5)') #=> '[a5-1230]'
242
+ # mem_operand('0(s1)') #=> '[s1+0]'
243
+ def mem_operand(mem)
244
+ m = mem.match(/\A(-?\d+)\((\w+)\)\z/)
245
+ raise_unsupported('memory operand', mem) if m.nil?
246
+
247
+ "[#{m[2]}#{format('%+d', Integer(m[1]))}]"
248
+ end
249
+
250
+ # The value +pc+ holds while the instruction at {@cur_addr} runs: this arch
251
+ # reads its own address, with no pipeline bias to add.
252
+ def pc_value
253
+ libc_base + @cur_addr
254
+ end
255
+
256
+ class << self
257
+ # RV64 is 64-bit.
258
+ # @return [Integer]
259
+ def bits
260
+ 64
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
@@ -3,9 +3,9 @@
3
3
  module OneGadget
4
4
  module Emulators
5
5
  # Arch-independent catalog of libc calls the emulator accepts without
6
- # executing them (see {Processor#dispatch_safe_call} for how an entry's
7
- # per-argument requirements are applied and what each requirement symbol
8
- # means). These functions -- syscall wrappers and +posix_spawn+'s setup
6
+ # executing them (see {COMMON} for what each requirement means, and
7
+ # {Processor#dispatch_safe_call} for how they are applied). These
8
+ # functions -- syscall wrappers and +posix_spawn+'s setup
9
9
  # helpers -- have identical semantics on every architecture, so their
10
10
  # requirements live here once instead of being copied into each arch's
11
11
  # emulator, keeping the arches from drifting.
@@ -14,6 +14,12 @@ module OneGadget
14
14
  # the specific +posix_spawnattr_setsigmask+/+setsigdefault+ keys precede the
15
15
  # generic +posix_spawnattr_+ prefix.
16
16
  module SafeCalls
17
+ # What each requirement asks of the caller:
18
+ # * +:global_var?+ -- nothing; it must already hold, or the candidate is aborted.
19
+ # * +:closed_fd+ -- nothing; the descriptor is recorded (see {Processor#note_closed_fd}).
20
+ # * +:null+, +:nullable_deref+ -- +<arg> == NULL+.
21
+ # * +:deref+ -- +readable: <arg>+.
22
+ # * +:writable+ -- +writable: <arg>+.
17
23
  # @return [Hash{String => Hash{Integer => Symbol}}]
18
24
  # Function name (or name prefix) => argument index => requirement.
19
25
  COMMON = {
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'one_gadget/emulators/lambda'
4
+
5
+ module OneGadget
6
+ module Emulators
7
+ # The memory a candidate reads and writes. Every store is filed under the base
8
+ # its address is an offset from -- the stack pointer, the frame pointer, or any
9
+ # register holding a pointer the caller supplies -- so a later load of the same
10
+ # slot reads back what this candidate put there, and a load of an untouched one
11
+ # is answered as what the caller left. Mixed into {Processor}.
12
+ module TrackedMemory
13
+ # @return [Hash{Integer => OneGadget::Emulators::Lambda}] Memory written through +sp+.
14
+ def sp_based_stack = get_corresponding_stack(sp)
15
+
16
+ # @return [Hash{Integer => Lambda}, nil] Memory written through {Processor#bp}, or nil when the arch has none.
17
+ def bp_based_stack = bp && get_corresponding_stack(bp)
18
+
19
+ # Enable frame-pointer stack tracking with +bp+ as the frame register, so a
20
+ # gadget staging data at +[bp+imm]+ (e.g. an argv array off the frame
21
+ # pointer) is recovered instead of collapsing to a bare +writable:+. A nil
22
+ # +bp+ leaves the arch +sp+-only. Call from the arch initializer after +super+.
23
+ # @param [String, nil] bp The frame register's name, or nil for an arch with none.
24
+ # @return [void]
25
+ def setup_frame_pointer(bp)
26
+ @bp = bp
27
+ end
28
+
29
+ # Where +address+ lands in the memory this emulator tracks: the stack it
30
+ # falls in and its offset within it. A load or store passes the address it
31
+ # dereferences, i.e. its operand with that dereference peeled off.
32
+ # @param [Lambda, String] address An address.
33
+ # @return [(Hash{Integer => Lambda}?, Integer)] The stack, +nil+ if none
34
+ # tracks this address, and the offset to index it at.
35
+ # @example an offset from a register
36
+ # resolve_address(Lambda.parse('rsp+0x10')) #=> [sp_based_stack, 0x10]
37
+ # @example an offset from a pointer no register names
38
+ # resolve_address(Lambda.parse('[rbp-0x48]+0x8')) #=> [the "[rbp-0x48]" stack, 0x8]
39
+ def resolve_address(address)
40
+ base, offset = address_base(address)
41
+ [get_corresponding_stack(base), offset]
42
+ end
43
+
44
+ # The memory +base+ addresses: what this candidate has written through it,
45
+ # keyed by offset. Every base gets one -- the stack pointer, the frame
46
+ # pointer, any other register, and a value no register names at all (a
47
+ # pointer the candidate derived and then built an array through).
48
+ #
49
+ # Keyed by how the base renders, which is what makes one store enough: a
50
+ # register that gets reassigned addresses somewhere else and renders
51
+ # differently, so it lands on a different key without any invalidation to
52
+ # arrange. Only a store overwriting what the base itself reads from would
53
+ # break that, which a candidate short enough to be a gadget doesn't do.
54
+ # @example (amd64) After +mov QWORD PTR [rsp+0x10], rdi+, keyed by offset.
55
+ # get_corresponding_stack('rsp') #=> { 0x10 => rdi }
56
+ # @param [String, Lambda] base A base, as {#resolve_address} yields it --
57
+ # not an offset expression, whose offset belongs in the key it indexes.
58
+ # @return [Hash{Integer => Lambda}, nil] nil when +base+ names nothing this
59
+ # emulator tracks memory for.
60
+ def get_corresponding_stack(base)
61
+ return nil unless base.is_a?(OneGadget::Emulators::Lambda) || registers.key?(base.to_s)
62
+
63
+ tracked_memory[base.to_s]
64
+ end
65
+
66
+ # The values this candidate wrote through an address built from +text+, which
67
+ # the array reported at +text+ therefore holds -- wherever in it they landed.
68
+ # A reader told about that array has to be told about these too, or it is
69
+ # described as though the gadget had not written to it.
70
+ # @param [String] text How the address is named.
71
+ # @return [Array<String>] Each value written, rendered, without duplicates.
72
+ # @example (riscv64) a store of +s8+ through +((a5 << 0x3) + [sp+0xd0])+
73
+ # writes_through('[sp+0xd0]') #=> ['s8']
74
+ def writes_through(text)
75
+ return [] if text.empty?
76
+
77
+ derived_writes.filter_map do |base, values|
78
+ next unless operands_of(base).any? { |operand| operand.to_s == text }
79
+
80
+ # A literal is not the caller's to arrange -- it is already what it is,
81
+ # and the NULL such a loop writes to terminate the array is one.
82
+ values.grep_v(Integer).map(&:to_s)
83
+ end.flatten.uniq
84
+ end
85
+
86
+ private
87
+
88
+ # Every base this candidate has written through, each mapped to the memory
89
+ # it addresses. See {#get_corresponding_stack}, which is how it is reached.
90
+ # @return [Hash{String => Hash{Integer => Lambda}}]
91
+ def tracked_memory
92
+ @tracked_memory ||= Hash.new { |memory, base| memory[base] = tracked_stack(base) }
93
+ end
94
+
95
+ # Split +address+ into the base it is offset from and that offset, in the
96
+ # forms {#get_corresponding_stack} and a tracked stack expect.
97
+ # @param [Lambda, String] address See {#resolve_address}.
98
+ # @return [(String, Lambda), Integer]
99
+ def address_base(address)
100
+ return [address, 0] unless address.is_a?(OneGadget::Emulators::Lambda)
101
+ # Still a dereference deep, so the whole thing names one value rather than
102
+ # an offset from anything: its own immediate is part of that name.
103
+ return [address, 0] if address.deref_count.positive?
104
+ # An operation's +obj+ is only the value it operates on, which addresses
105
+ # somewhere else entirely -- a candidate building an array through
106
+ # +(rsi & ~0xf)+ is not writing through +rsi+.
107
+ return [address.dup.tap { |base| base.immi = 0 }, address.immi] if address.operation?
108
+
109
+ [address.obj, address.immi]
110
+ end
111
+
112
+ # An always-on tracked stack keyed by offset: a Hash that lazily materialises
113
+ # +[reg+off]+ as a one-deref {Lambda}. Used for the +sp+- and {Processor#bp}-based stacks.
114
+ def tracked_stack(reg)
115
+ Hash.new do |h, k|
116
+ h[k] = OneGadget::Emulators::Lambda.new(reg).tap do |lmda|
117
+ lmda.immi = k
118
+ lmda.deref!
119
+ end
120
+ end
121
+ end
122
+
123
+ # The value an instruction reads through +val+: what this candidate put at
124
+ # that address, when it is one the candidate has written, and the
125
+ # dereference itself otherwise (recording the read, see {#note_read}).
126
+ #
127
+ # A slot the gadget fills in reads back as what was put there, so a
128
+ # constraint on it names the value the caller has to arrange rather than
129
+ # whatever the slot held on entry -- which the gadget has already replaced.
130
+ # @param [Object] val The operand's value, as produced by {#arg_to_lambda}.
131
+ # @return [Object]
132
+ # @example (arm) +str r3, [sp, #4]+ then +ldr r0, [sp, #4]+ reads back r3
133
+ def read_value(val)
134
+ stored = stored_value(val)
135
+ return stored unless stored.nil?
136
+
137
+ note_read(val)
138
+ val
139
+ end
140
+
141
+ # What this candidate stored at the address +val+ dereferences, or +nil+ if
142
+ # it stored nothing there. Only a single dereference of an address this
143
+ # emulator tracks names a slot it can answer for (see {#resolve_address}).
144
+ # @param [Object] val
145
+ # @return [Object, nil]
146
+ def stored_value(val)
147
+ return nil unless val.is_a?(OneGadget::Emulators::Lambda) && val.deref_count.positive?
148
+
149
+ stack, offset = resolve_address(val.dup.ref!)
150
+ stack&.key?(offset) ? stack[offset] : nil
151
+ end
152
+
153
+ # The innermost base name of a (possibly nested or dereferenced) address
154
+ # lambda, following +obj+ through any nested lambdas.
155
+ # @param [OneGadget::Emulators::Lambda] lmda
156
+ # @return [String, nil] The root base name, or +nil+ for an absolute address.
157
+ # @example
158
+ # root_base(arg_to_lambda('[[$base+0x10]+0x8]')) #=> '$base'
159
+ # root_base(arg_to_lambda('x19+0xed8')) #=> 'x19'
160
+ def root_base(lmda)
161
+ obj = lmda.obj
162
+ obj = obj.obj while obj.is_a?(OneGadget::Emulators::Lambda)
163
+ obj
164
+ end
165
+
166
+ # Stores whose address is an operation rather than a base and a displacement
167
+ # -- a scaled index added to a pointer, say. Such a write lands somewhere in
168
+ # whatever that pointer addresses, at an offset only a value the caller
169
+ # supplies decides, so it is tracked under a base of its own that nothing
170
+ # reads back (see {#writes_through}).
171
+ # @return [Array<(Lambda, Array)>] Each address operation, and what went there.
172
+ def derived_writes
173
+ @derived_writes ||= []
174
+ end
175
+
176
+ # The values an operation is built from, flattened out of its tree, so an
177
+ # operand is recognised as itself rather than as text inside a rendering
178
+ # (where +x3+ would be found in +x30+).
179
+ # @param [Lambda, Object] lmda An address, or one part of one.
180
+ # @return [Array] Its leaf operands, or +lmda+ itself when it is not an operation.
181
+ def operands_of(lmda)
182
+ return [lmda] unless lmda.is_a?(OneGadget::Emulators::Lambda) && lmda.operation?
183
+
184
+ operands_of(lmda.obj) + operands_of(lmda.rhs)
185
+ end
186
+
187
+ # Track a store: write +values+ (one per word from +dst_l+) into the stack
188
+ # {#resolve_address} resolves +dst_l+ to, and require +dst_l+
189
+ # writable -- unless it is a pure +sp+ store. +sp+ is invariantly the
190
+ # writable stack; the frame pointer only conventionally is, so a store
191
+ # through it stays a real precondition (like amd64's +writable: rbp+imm+).
192
+ # @param [OneGadget::Emulators::Lambda] dst_l The destination, zero-deref.
193
+ # @param [Array<OneGadget::Emulators::Lambda, Integer>] values One per word.
194
+ # @return [void]
195
+ def track_write(dst_l, *values)
196
+ stack, offset = resolve_address(dst_l)
197
+ base, = address_base(dst_l)
198
+ derived_writes << [base, values] if base.is_a?(OneGadget::Emulators::Lambda) && base.operation?
199
+ values.each_with_index { |v, i| stack[offset + size_t * i] = v } if stack
200
+ add_writable(dst_l) unless stack.equal?(sp_based_stack)
201
+ end
202
+
203
+ # Resolve +sp+- and (when tracked) {Processor#bp}-relative operands to their offset.
204
+ def eval_dict
205
+ bp ? { sp => 0, bp => 0 } : { sp => 0 }
206
+ end
207
+ end
208
+ end
209
+ end
@@ -10,6 +10,10 @@ module OneGadget
10
10
  # Super class for amd64 and i386 processor.
11
11
  class X86 < Processor
12
12
  # Constructor for a x86 processor.
13
+ # @param [Array<String>] registers All the register names this architecture accepts.
14
+ # @param [String] sp The stack pointer's name.
15
+ # @param [String] bp The frame pointer's name.
16
+ # @param [String] pc The program counter's name.
13
17
  def initialize(registers, sp, bp, pc)
14
18
  super(registers, sp)
15
19
  @pc = pc
@@ -30,8 +34,7 @@ module OneGadget
30
34
  return handle_branch(mnem, cmd) != :fail if branch_mnem?(mnem)
31
35
 
32
36
  inst, args = parse(cmd)
33
- sym = :"inst_#{inst.inst}"
34
- __send__(sym, *args) != :fail
37
+ __send__(inst.handler, *args) != :fail
35
38
  end
36
39
 
37
40
  # x86 conditional-jump mnemonics mapped to shared {Conditional::RELATION}
@@ -51,14 +54,11 @@ module OneGadget
51
54
  COMPARES = { 'cmp' => :sub, 'test' => :and }.freeze
52
55
 
53
56
  # A segment-prefixed operand reads thread-local storage, which isn't modelled
54
- # (nor is its counterpart on the other arches -- aarch64's +mrs tpidr_el0+
55
- # and arm's +mrc p15+ are unsupported instructions, so those paths already
56
- # abort). Every gadget observed behind one tests +errno == ENOEXEC+ on
57
- # glibc's +execvpe+ path -- a value the caller would have to have arranged
58
- # beforehand, since the gadget is entered after the +execve+ that would set
59
- # it -- so modelling this only produces gadgets that all but never apply.
60
- # Worth revisiting if a libc is found reaching a terminal call under a
61
- # condition that commonly holds, e.g. +errno != <some error>+.
57
+ # (the other arches reach it by instructions that are unsupported anyway).
58
+ # Every gadget observed behind one tests +errno == ENOEXEC+, which the caller
59
+ # would have had to arrange before entering, so modelling it would produce
60
+ # gadgets that all but never apply. Worth revisiting for a libc found
61
+ # reaching a terminal call under a condition that commonly holds.
62
62
  # @example +cmp DWORD PTR fs:[r14], 0x8+ -- errno == ENOEXEC
63
63
  SEGMENT_OPERAND = /\b(?:fs|gs|ds|es|ss|cs):/
64
64
 
@@ -125,6 +125,8 @@ module OneGadget
125
125
  branch_on_compare(JCC[mnem], jump_target(cmd))
126
126
  end
127
127
 
128
+ # The counter register a +jcxz+-family branch tests, at the width its
129
+ # mnemonic names.
128
130
  def cx_reg(mnem)
129
131
  { 'jcxz' => 'cx', 'jecxz' => 'ecx', 'jrcxz' => 'rcx' }[mnem]
130
132
  end
@@ -267,10 +269,17 @@ module OneGadget
267
269
  registers[dst] = 0
268
270
  end
269
271
 
272
+ # A {Lambda} knows how to add a number to itself, not the other way round,
273
+ # and addition commutes -- adding an unknown value to a known one is the
274
+ # same unknown value shifted (see {DataProcessing#offset_result}, which
275
+ # states the same rule for the architectures that go through it).
270
276
  def inst_add(dst, src)
271
277
  check_register!(dst)
272
278
 
273
- registers[dst] += read_value(arg_to_lambda(src))
279
+ lhs = registers[dst]
280
+ rhs = read_value(arg_to_lambda(src))
281
+ lhs, rhs = rhs, lhs if lhs.is_a?(Integer)
282
+ registers[dst] = lhs + rhs
274
283
  end
275
284
 
276
285
  # +and dst, src+ both writes +dst+ and sets the flags a following branch
@@ -303,22 +312,20 @@ module OneGadget
303
312
  registers[dst] -= src
304
313
  end
305
314
 
306
- # yap, nop
307
- def inst_nop(*); end
308
-
309
315
  # A marker for the branch predictor: it says a jump may land here and leaves
310
316
  # every value alone.
311
317
  def inst_endbr64(*); end
312
318
  alias inst_endbr32 inst_endbr64
313
319
 
314
- # Swap what two registers hold. Two forms are not that, and stay refused: a
315
- # memory operand is an exchange with memory (and an atomic one), and a
316
- # narrower view names part of a register, which swapping whole values cannot
317
- # express. Naming one place twice is nothing at all, whatever it names --
318
- # that is the multi-byte nop a compiler pads with.
319
- # @example
320
- # xchg ebx,edi -- a swap
321
- # xchg ax,ax -- padding
320
+ # Swap what two registers hold. Naming one place twice is nothing at all,
321
+ # whatever it names -- the multi-byte nop a compiler pads with.
322
+ # @raise [OneGadget::Error::UnsupportedInstructionArgumentError]
323
+ # For a memory operand, which is an exchange with memory and an atomic one,
324
+ # or a narrower view, which names part of a register where swapping whole
325
+ # values cannot reach.
326
+ # @example (amd64) A swap, then the padding.
327
+ # inst_xchg('rbx', 'rdi') #=> [rdi, rbx]
328
+ # inst_xchg('rax', 'rax') #=> nil
322
329
  def inst_xchg(dst, src)
323
330
  return if dst == src
324
331
  raise Error::UnsupportedInstructionArgumentError, "xchg #{dst},#{src}" unless
@@ -334,6 +341,9 @@ module OneGadget
334
341
  dispatch_safe_call(addr)
335
342
  end
336
343
 
344
+ # A vector register holds one value per lane, so it becomes an array of
345
+ # them -- each lane named by the shift that reaches it -- where an ordinary
346
+ # register becomes a single {Lambda}.
337
347
  def to_lambda(reg)
338
348
  return super unless reg =~ /^xmm\d+$/
339
349
 
@@ -46,33 +46,6 @@ module OneGadget
46
46
  def call_str
47
47
  'bl'
48
48
  end
49
-
50
- def bin_sh_offset
51
- @bin_sh_offset ||= str_offset('/bin/sh')
52
- end
53
-
54
- def str_bin_sh?(str)
55
- str.include?('$base') && str.include?(bin_sh_offset.to_s(16))
56
- end
57
-
58
- # Offset of the standalone "sh" string (\0-preceded and \0-terminated) that
59
- # glibc passes as argv[0] in execl("/bin/sh", "sh", ...). Its distance from
60
- # "/bin/sh" is build-specific, so locate it directly instead of guessing.
61
- # +nil+ when the libc has no such string.
62
- def sh_offset
63
- return @sh_offset if defined?(@sh_offset)
64
-
65
- idx = File.binread(file).index("\x00sh\x00")
66
- @sh_offset = idx && idx + 1
67
- end
68
-
69
- def str_sh?(str)
70
- !sh_offset.nil? && str.include?('$base') && str.include?(sh_offset.to_s(16))
71
- end
72
-
73
- def global_var?(str)
74
- base_relative?(str, '$base')
75
- end
76
49
  end
77
50
  end
78
51
  end
@@ -12,30 +12,6 @@ module OneGadget
12
12
  def emulator
13
13
  OneGadget::Emulators::Amd64.new
14
14
  end
15
-
16
- # The branch-aware walker (see {Base#branch_aware_candidates}) already
17
- # stitches +jmp+ targets, so only the filter remains.
18
- def candidates
19
- super do |candidate|
20
- next true if candidate.include?('posix_spawn@')
21
- next false unless candidate.include?(bin_sh_hex) # works in x86-64
22
- next false unless candidate.lines.last.include?('execve') # only care execve
23
-
24
- true
25
- end
26
- end
27
-
28
- def bin_sh_hex
29
- @bin_sh_hex ||= str_offset('/bin/sh').to_s(16)
30
- end
31
-
32
- def str_bin_sh?(str)
33
- str.include?('$base') && str.include?(bin_sh_hex)
34
- end
35
-
36
- def global_var?(str)
37
- base_relative?(str, '$base')
38
- end
39
15
  end
40
16
  end
41
17
  end