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
|
@@ -0,0 +1,289 @@
|
|
|
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 MIPS (32-bit, o32).
|
|
10
|
+
class Mips < Processor
|
|
11
|
+
# Instantiate a {Mips} object.
|
|
12
|
+
def initialize
|
|
13
|
+
super(OneGadget::ABI.mips, 'sp')
|
|
14
|
+
@registers['zero'] = 0 # hardwired
|
|
15
|
+
@pc = 'pc'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# This arch compares and branches in one instruction -- there is no flag
|
|
19
|
+
# register -- so each mnemonic names the relation its operands must stand in
|
|
20
|
+
# for the branch to be taken. The spellings against zero name one operand;
|
|
21
|
+
# +zero+ is the other, and reads as the +0x0+ it holds.
|
|
22
|
+
# @return [Hash{String => Symbol}]
|
|
23
|
+
COND = {
|
|
24
|
+
'beq' => :eq, 'bne' => :ne,
|
|
25
|
+
'beqz' => :eq, 'bnez' => :ne,
|
|
26
|
+
'blez' => :sle, 'bgtz' => :sgt, 'bltz' => :slt, 'bgez' => :sge
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
# The data-processing instructions, mapped to the Ruby operator that folds
|
|
30
|
+
# them. An immediate form differs from its register one only in how the right
|
|
31
|
+
# operand is written, which the operand reader already handles, so both name
|
|
32
|
+
# the same operator. Arithmetic shift right is absent: a value is held masked
|
|
33
|
+
# to its width, where +>>+ is the logical shift.
|
|
34
|
+
DATA_OPS = {
|
|
35
|
+
'and' => :&, 'andi' => :&,
|
|
36
|
+
'or' => :|, 'ori' => :|,
|
|
37
|
+
'xor' => :^, 'xori' => :^,
|
|
38
|
+
'sll' => :<<, 'srl' => :>>
|
|
39
|
+
}.freeze
|
|
40
|
+
private_constant :DATA_OPS
|
|
41
|
+
|
|
42
|
+
# The loads, mapped to how many bytes each reads.
|
|
43
|
+
LOADS = { 'lw' => 4, 'lh' => 2, 'lhu' => 2, 'lb' => 1, 'lbu' => 1 }.freeze
|
|
44
|
+
private_constant :LOADS
|
|
45
|
+
|
|
46
|
+
# The stores, mapped to how many bytes each writes.
|
|
47
|
+
STORES = { 'sw' => 4, 'sh' => 2, 'sb' => 1 }.freeze
|
|
48
|
+
private_constant :STORES
|
|
49
|
+
|
|
50
|
+
# Supported instruction set. Anything not listed aborts the candidate.
|
|
51
|
+
# @return [Array<Instruction>] The supported instructions.
|
|
52
|
+
def instructions
|
|
53
|
+
[
|
|
54
|
+
Instruction.new('addiu', 3),
|
|
55
|
+
Instruction.new('addu', 3),
|
|
56
|
+
Instruction.new('bal', 1),
|
|
57
|
+
Instruction.new('jal', 1),
|
|
58
|
+
Instruction.new('jalr', 1..2),
|
|
59
|
+
Instruction.new('li', 2),
|
|
60
|
+
Instruction.new('lui', 2),
|
|
61
|
+
Instruction.new('move', 2),
|
|
62
|
+
Instruction.new('nop', 0),
|
|
63
|
+
Instruction.new('subu', 3)
|
|
64
|
+
] + (LOADS.keys + STORES.keys).map { |mnem| Instruction.new(mnem, 2) } +
|
|
65
|
+
DATA_OPS.keys.map { |mnem| Instruction.new(mnem, 3) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The value of a call's +idx+-th argument. o32 states the first four in
|
|
69
|
+
# registers, and reserves a stack slot for every argument including those --
|
|
70
|
+
# so an argument's slot is its index however it is passed, and the ones past
|
|
71
|
+
# the registers are read from there.
|
|
72
|
+
# @param [Integer] idx The 0-based index of the argument.
|
|
73
|
+
# @return [Lambda, Integer]
|
|
74
|
+
def argument(idx)
|
|
75
|
+
return registers["a#{idx}"] if idx < ARG_REGISTERS
|
|
76
|
+
|
|
77
|
+
top = registers['sp'].evaluate('sp' => 0)
|
|
78
|
+
sp_based_stack[top + (idx * size_t)]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Emulate one instruction, holding back any transfer of control.
|
|
82
|
+
#
|
|
83
|
+
# This arch delays every transfer by one instruction: whatever follows a
|
|
84
|
+
# branch or a call runs *before* it takes effect. So a transfer is held back
|
|
85
|
+
# here and applied once that instruction has run, which is the order they
|
|
86
|
+
# really happen in -- and it leaves both addresses of the pair meaning what
|
|
87
|
+
# they say, since entering at the transfer runs both, while entering at the
|
|
88
|
+
# instruction after it runs only that one.
|
|
89
|
+
# @param [String] cmd One line from result of objdump.
|
|
90
|
+
# @return [Boolean] If successfully processed.
|
|
91
|
+
# @see OneGadget::Emulators::X86#process!
|
|
92
|
+
def process!(cmd)
|
|
93
|
+
resolve_pending_branch(cmd)
|
|
94
|
+
@cur_addr = cmd[/\A\s*([0-9a-f]+):/, 1]&.to_i(16)
|
|
95
|
+
|
|
96
|
+
@got_value = cmd[GOT_VALUE, 1]&.to_i(16)
|
|
97
|
+
|
|
98
|
+
mnem = mnemonic(cmd)
|
|
99
|
+
if transfer?(mnem)
|
|
100
|
+
@delayed = [mnem, cmd]
|
|
101
|
+
return true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
dispatch(cmd) && apply_delayed
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# How many arguments this ABI states in registers.
|
|
108
|
+
ARG_REGISTERS = 4
|
|
109
|
+
private_constant :ARG_REGISTERS
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
# What a GOT slot holds, stated beside the load by
|
|
114
|
+
# +OneGadget::Fetchers::Mips+ because this architecture reaches its globals
|
|
115
|
+
# through that table rather than through the instruction's own address.
|
|
116
|
+
GOT_VALUE = /#\s*([0-9a-f]+)\s*\z/
|
|
117
|
+
private_constant :GOT_VALUE
|
|
118
|
+
|
|
119
|
+
# The calls, which are delayed exactly as the branches are.
|
|
120
|
+
CALLS = %w[jal jalr bal].freeze
|
|
121
|
+
private_constant :CALLS
|
|
122
|
+
|
|
123
|
+
def branch_mnem?(mnem) = mnem == 'b' || COND.key?(mnem)
|
|
124
|
+
|
|
125
|
+
# Whether +mnem+ transfers control, and so takes effect one instruction late.
|
|
126
|
+
def transfer?(mnem) = branch_mnem?(mnem) || CALLS.include?(mnem)
|
|
127
|
+
|
|
128
|
+
def dispatch(cmd)
|
|
129
|
+
inst, args = parse(cmd)
|
|
130
|
+
__send__(inst.handler, *args) != :fail
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Carry out the transfer held back from the previous instruction, now that
|
|
134
|
+
# the instruction it delays behind has run.
|
|
135
|
+
# @return [Boolean]
|
|
136
|
+
def apply_delayed
|
|
137
|
+
return true if @delayed.nil?
|
|
138
|
+
|
|
139
|
+
mnem, cmd = @delayed
|
|
140
|
+
@delayed = nil
|
|
141
|
+
return handle_branch(mnem, cmd) != :fail if branch_mnem?(mnem)
|
|
142
|
+
|
|
143
|
+
dispatch(cmd)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Operands of +cmd+ (mnemonic dropped), each stripped of a trailing +<symbol>+.
|
|
147
|
+
def operands(cmd)
|
|
148
|
+
cmd.sub(/\A[0-9a-f]+:\s*\S+\s*/, '').split(',').map { |o| o.strip.sub(/\s*<.*>\z/, '') }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Record the comparison and decide the branch together, since one
|
|
152
|
+
# instruction is both (see {COND}).
|
|
153
|
+
def handle_branch(mnem, cmd)
|
|
154
|
+
return true if mnem == 'b' # unconditional: control handled by the stitched path
|
|
155
|
+
|
|
156
|
+
ops = operands(cmd)
|
|
157
|
+
lhs, rhs = mnem.end_with?('z') ? [ops[0], 'zero'] : ops[0..1]
|
|
158
|
+
record_compare(:sub, operand_str(lhs), operand_str(rhs))
|
|
159
|
+
branch_on_compare(COND[mnem], ops.last.to_i(16))
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# A call: record the terminal +exec*+ target, accept a known-safe libc call,
|
|
163
|
+
# or +:fail+ to abort the candidate. This arch reaches most of libc through a
|
|
164
|
+
# register, so the name comes from what the fetcher wrote beside it
|
|
165
|
+
# (+OneGadget::Fetchers::Mips+); a call it could not name is one the emulator
|
|
166
|
+
# cannot reason about either, which {Processor#dispatch_safe_call} refuses.
|
|
167
|
+
# @param [String] addr The call target, as the line names it.
|
|
168
|
+
# @return [nil, :fail]
|
|
169
|
+
def call_target(addr)
|
|
170
|
+
return reach_terminal_call(addr) if terminal_call?(addr)
|
|
171
|
+
|
|
172
|
+
dispatch_safe_call(addr)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# +jalr+ takes the register holding the target, optionally preceded by the
|
|
176
|
+
# register the return address goes to; the target is the last either way.
|
|
177
|
+
def inst_jalr(*args) = call_target(args.last)
|
|
178
|
+
|
|
179
|
+
# A direct call, which states its target outright.
|
|
180
|
+
def inst_jal(addr) = call_target(addr)
|
|
181
|
+
|
|
182
|
+
# +bal+ is the pc-relative direct call; it names its target as +jal+ does.
|
|
183
|
+
def inst_bal(addr) = call_target(addr)
|
|
184
|
+
|
|
185
|
+
def inst_nop = true
|
|
186
|
+
|
|
187
|
+
def inst_move(dst, src)
|
|
188
|
+
check_register!(dst)
|
|
189
|
+
|
|
190
|
+
registers[dst] = arg_to_lambda(src)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# +li+ is the assembler's spelling of loading a constant, whatever the
|
|
194
|
+
# instructions it expands to.
|
|
195
|
+
def inst_li(dst, imm)
|
|
196
|
+
check_register!(dst)
|
|
197
|
+
|
|
198
|
+
registers[dst] = Integer(imm)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# +lui dst, imm+ loads the immediate into the upper half of the register.
|
|
202
|
+
def inst_lui(dst, imm)
|
|
203
|
+
check_register!(dst)
|
|
204
|
+
|
|
205
|
+
registers[dst] = (imm.to_i(16) << 16) & width_mask
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# +addu dst, src, op2+; +addiu+ differs only in that its right operand is
|
|
209
|
+
# written as a literal. Neither traps on overflow, which is why the compiler
|
|
210
|
+
# uses them for address arithmetic and the trapping forms are not modelled.
|
|
211
|
+
def inst_addu(dst, src, op2) = arith(:+, dst, src, op2)
|
|
212
|
+
alias inst_addiu inst_addu
|
|
213
|
+
|
|
214
|
+
# +subu dst, src, op2+. See {#inst_addu}.
|
|
215
|
+
def inst_subu(dst, src, op2) = arith(:-, dst, src, op2)
|
|
216
|
+
|
|
217
|
+
# Each data-processing mnemonic handled the one way, since they differ only
|
|
218
|
+
# in the operator applied.
|
|
219
|
+
DATA_OPS.each do |mnem, op|
|
|
220
|
+
define_method(Instruction.handler_name(mnem)) do |dst, src, op2|
|
|
221
|
+
data_op(op, dst, src, op2, name: mnem)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
LOADS.each do |mnem, size|
|
|
226
|
+
define_method(Instruction.handler_name(mnem)) { |dst, mem| load_value(dst, mem, size) }
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
STORES.each do |mnem, size|
|
|
230
|
+
define_method(Instruction.handler_name(mnem)) { |src, mem| store_value(src, mem, size) }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# A load. The address is read like any other, so what the caller has to
|
|
234
|
+
# arrange about it is recorded the same way. A load narrower than a register
|
|
235
|
+
# only takes part of the word this emulator tracks, which is not a value it
|
|
236
|
+
# can name, so the register then holds what a call would have left.
|
|
237
|
+
# @param [String] dst The destination register.
|
|
238
|
+
# @param [String] mem The memory operand, as written.
|
|
239
|
+
# @param [Integer] size How many bytes the load reads.
|
|
240
|
+
# @return [void]
|
|
241
|
+
def load_value(dst, mem, size)
|
|
242
|
+
check_register!(dst)
|
|
243
|
+
# a GOT slot holds an address, and the file says which
|
|
244
|
+
return registers[dst] = libc_base + @got_value if @got_value && size == size_t
|
|
245
|
+
|
|
246
|
+
value = read_value(arg_to_lambda(mem_operand(mem)))
|
|
247
|
+
registers[dst] = size == size_t ? value : clobbered_value
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# A store, tracked so a later load of the same slot reads it back, and
|
|
251
|
+
# requiring its target writable ({Processor#track_write}). A store narrower
|
|
252
|
+
# than a register leaves the rest of the slot as it was, so what the slot
|
|
253
|
+
# then holds is named as unknown rather than as the value stored.
|
|
254
|
+
# @param [String] src The register holding the value stored.
|
|
255
|
+
# @param [String] mem The memory operand, as written.
|
|
256
|
+
# @param [Integer] size How many bytes the store writes.
|
|
257
|
+
# @return [void]
|
|
258
|
+
def store_value(src, mem, size)
|
|
259
|
+
check_register!(src)
|
|
260
|
+
|
|
261
|
+
dst_l = arg_to_lambda(mem_operand(mem)).ref!
|
|
262
|
+
track_write(dst_l, size == size_t ? registers[src] : clobbered_value)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# This arch writes a memory operand as an offset applied to one register,
|
|
266
|
+
# which the {Lambda} parser reads in its bracketed form. The offset is
|
|
267
|
+
# decimal, as objdump prints it.
|
|
268
|
+
# @param [String] mem The operand, as written.
|
|
269
|
+
# @return [String]
|
|
270
|
+
# @example
|
|
271
|
+
# mem_operand('-31652(gp)') #=> '[gp-31652]'
|
|
272
|
+
# mem_operand('4(s6)') #=> '[s6+4]'
|
|
273
|
+
def mem_operand(mem)
|
|
274
|
+
m = mem.match(/\A(-?\d+)\((\w+)\)\z/)
|
|
275
|
+
raise_unsupported('memory operand', mem) if m.nil?
|
|
276
|
+
|
|
277
|
+
"[#{m[2]}#{format('%+d', Integer(m[1]))}]"
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
class << self
|
|
281
|
+
# o32 is 32-bit.
|
|
282
|
+
# @return [Integer]
|
|
283
|
+
def bits
|
|
284
|
+
32
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|