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
|
@@ -15,15 +15,30 @@ require 'one_gadget/gadget'
|
|
|
15
15
|
# <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
|
|
16
16
|
|
|
17
17
|
build_id = File.basename(__FILE__, '.rb').split('-').last
|
|
18
|
+
OneGadget::Gadget.add(build_id, 310192,
|
|
19
|
+
constraints: ["readable: x22", "x0 != 0x1", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
|
|
20
|
+
effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
|
|
18
21
|
OneGadget::Gadget.add(build_id, 310192,
|
|
19
22
|
constraints: ["readable: x22", "x0 == 0x1", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
|
|
20
23
|
effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
|
|
24
|
+
OneGadget::Gadget.add(build_id, 310196,
|
|
25
|
+
constraints: ["readable: x22", "x0 != 0x1", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
|
|
26
|
+
effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
|
|
21
27
|
OneGadget::Gadget.add(build_id, 310196,
|
|
22
28
|
constraints: ["readable: x22", "x0 == 0x1", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
|
|
23
29
|
effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
|
|
30
|
+
OneGadget::Gadget.add(build_id, 310200,
|
|
31
|
+
constraints: ["readable: x22", "x0 != 0x1", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
|
|
32
|
+
effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
|
|
24
33
|
OneGadget::Gadget.add(build_id, 310200,
|
|
25
34
|
constraints: ["readable: x22", "x0 == 0x1", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
|
|
26
35
|
effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
|
|
36
|
+
OneGadget::Gadget.add(build_id, 310208,
|
|
37
|
+
constraints: ["readable: x22", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
|
|
38
|
+
effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
|
|
39
|
+
OneGadget::Gadget.add(build_id, 310212,
|
|
40
|
+
constraints: ["readable: x22", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
|
|
41
|
+
effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
|
|
27
42
|
OneGadget::Gadget.add(build_id, 310216,
|
|
28
43
|
constraints: ["readable: x22", "{\"sh\", \"-c\", \"--\", x21, ...} is a valid argv", "sp+0x218 == NULL || (u16)[sp+0x218] == 0x0"],
|
|
29
44
|
effect: "posix_spawn(sp+0xc, \"/bin/sh\", 0, sp+0x218, sp+0x50, environ)")
|
|
@@ -19,6 +19,8 @@ module OneGadget
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# @see OneGadget::Emulators::X86#process!
|
|
22
|
+
# @param [String] cmd One line from result of objdump.
|
|
23
|
+
# @return [Boolean] If successfully processed.
|
|
22
24
|
def process!(cmd)
|
|
23
25
|
resolve_pending_branch(cmd)
|
|
24
26
|
cmd = cmd.gsub(/#-?(0x)?[0-9a-f]+/) { |v| v[1..] }
|
|
@@ -27,8 +29,7 @@ module OneGadget
|
|
|
27
29
|
return handle_branch(mnem, cmd) != :fail if branch_mnem?(mnem)
|
|
28
30
|
|
|
29
31
|
inst, args = parse(cmd)
|
|
30
|
-
|
|
31
|
-
__send__(sym, *args) != :fail
|
|
32
|
+
__send__(inst.handler, *args) != :fail
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
# Supported instruction set.
|
|
@@ -61,6 +62,7 @@ module OneGadget
|
|
|
61
62
|
|
|
62
63
|
# A landing pad marks where an indirect branch may arrive; nothing about the
|
|
63
64
|
# state changes at one.
|
|
65
|
+
# @return [void]
|
|
64
66
|
alias inst_bti inst_nop
|
|
65
67
|
|
|
66
68
|
# Return the argument value of calling a function.
|
|
@@ -158,6 +160,7 @@ module OneGadget
|
|
|
158
160
|
|
|
159
161
|
class << self
|
|
160
162
|
# AArch64 is 64-bit.
|
|
163
|
+
# @return [Integer]
|
|
161
164
|
def bits
|
|
162
165
|
64
|
|
163
166
|
end
|
|
@@ -28,11 +28,15 @@ module OneGadget
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# Memoized bytes of +file+ (the target libc), shared across emulator instances.
|
|
31
|
+
# @param [String] file Path to the target libc.
|
|
32
|
+
# @return [String] Its bytes.
|
|
31
33
|
def self.file_data(file)
|
|
32
34
|
(@file_data ||= {})[file] ||= File.binread(file)
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
# @see OneGadget::Emulators::AArch64#process!
|
|
38
|
+
# @param [String] cmd One line from result of objdump.
|
|
39
|
+
# @return [Boolean] If successfully processed.
|
|
36
40
|
def process!(cmd)
|
|
37
41
|
resolve_pending_branch(cmd)
|
|
38
42
|
line = cmd.strip
|
|
@@ -45,7 +49,7 @@ module OneGadget
|
|
|
45
49
|
return __send__(:"inst_#{mnem}", rest) != :fail if %w[push pop].include?(mnem)
|
|
46
50
|
|
|
47
51
|
inst, args = parse(body)
|
|
48
|
-
__send__(
|
|
52
|
+
__send__(inst.handler, *args) != :fail
|
|
49
53
|
end
|
|
50
54
|
|
|
51
55
|
# The flag-setting spelling of an instruction we model, which differs from
|
|
@@ -101,17 +105,16 @@ module OneGadget
|
|
|
101
105
|
end
|
|
102
106
|
|
|
103
107
|
# Settle Thumb vs A32 from a whole candidate, before any of it is emulated.
|
|
104
|
-
# {#track_mode}
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
# name. Applying the same evidence up front removes that dependence on
|
|
108
|
-
# whatever happened to be processed first.
|
|
109
|
-
#
|
|
110
|
-
# A32 is left alone: it shows neither a width suffix nor a 2-byte stride, so
|
|
111
|
-
# a genuinely A32 candidate keeps the whole-word bias. A single-instruction
|
|
112
|
-
# Thumb candidate offers no evidence either and is no better served than
|
|
113
|
-
# before.
|
|
108
|
+
# {#track_mode} learns only from lines already seen, so the first instruction
|
|
109
|
+
# would otherwise be judged on no evidence -- and when it reads +pc+, that
|
|
110
|
+
# decides an address the constraints go on to name.
|
|
114
111
|
# @param [Array<String>] lines The candidate's objdump lines.
|
|
112
|
+
# @return [void]
|
|
113
|
+
# @example A 2-byte stride settles it as Thumb, where +pc+ reads four ahead
|
|
114
|
+
# of the instruction rather than eight.
|
|
115
|
+
# note_instruction_set(['4a1c0: ldr r0, [pc, #8]', '4a1c2: add r0, pc'])
|
|
116
|
+
# process('4a1c2: mov r0, pc')
|
|
117
|
+
# registers['r0'] #=> $base+0x4a1c6
|
|
115
118
|
def note_instruction_set(lines)
|
|
116
119
|
return if @thumb
|
|
117
120
|
|
|
@@ -133,12 +136,6 @@ module OneGadget
|
|
|
133
136
|
@prev_addr = @cur_addr
|
|
134
137
|
end
|
|
135
138
|
|
|
136
|
-
# Split an objdump line into its instruction body and the literal-pool address
|
|
137
|
-
# embedded in the trailing +@+ comment (used by PC-relative +ldr+).
|
|
138
|
-
# @return [(String, Integer?)] The instruction body, and the literal address (or +nil+).
|
|
139
|
-
# @example
|
|
140
|
-
# split_line('2c626: ldr r2, [pc, #128] @ (2c6a8 <x>)')
|
|
141
|
-
# #=> ['ldr r2, [pc, #128]', 0x2c6a8]
|
|
142
139
|
# The instruction body a line carries, in the plain form the generic parser
|
|
143
140
|
# expects, and the literal-pool address it points at. Both are decided by the
|
|
144
141
|
# text alone (see {Processor.line_memo}).
|
|
@@ -151,9 +148,20 @@ module OneGadget
|
|
|
151
148
|
end
|
|
152
149
|
end
|
|
153
150
|
|
|
151
|
+
# Split an objdump line into its instruction body and the literal-pool address
|
|
152
|
+
# embedded in the trailing +@+ comment (used by PC-relative +ldr+).
|
|
153
|
+
# @return [(String, Integer?)] The instruction body, and the literal address (or +nil+).
|
|
154
|
+
# @example The same load, as objdump writes it from an ELF and from bytes --
|
|
155
|
+
# it states the address differently when it cannot name what is there.
|
|
156
|
+
# split_line('2c626: ldr r2, [pc, #128] @ (2c6a8 <x>)')
|
|
157
|
+
# #=> ['ldr r2, [pc, #128]', 0x2c6a8]
|
|
158
|
+
# split_line('2c626: ldr r2, [pc, #128] @ (0x2c6a8)')
|
|
159
|
+
# #=> ['ldr r2, [pc, #128]', 0x2c6a8]
|
|
160
|
+
# split_line('2c626: ldr.w r2, [pc, #128] @ 2c6a8')
|
|
161
|
+
# #=> ['ldr.w r2, [pc, #128]', 0x2c6a8]
|
|
154
162
|
def split_line(line)
|
|
155
163
|
body = line.sub(/\A[0-9a-f]+:\s*/, '')
|
|
156
|
-
literal = body[/@\s*\(?([0-9a-f]+)\s/, 1]&.to_i(16)
|
|
164
|
+
literal = body[/@\s*\(?(?:0x)?([0-9a-f]+)(?:[\s)]|\z)/, 1]&.to_i(16)
|
|
157
165
|
# Strip a trailing comment. The marker is whitespace-prefixed, which avoids
|
|
158
166
|
# eating the +@@+ inside symbol names such as +<execve@@GLIBC_2.4>+.
|
|
159
167
|
[body.sub(/\s+[@;].*\z/, '').strip, literal]
|
|
@@ -297,6 +305,7 @@ module OneGadget
|
|
|
297
305
|
|
|
298
306
|
class << self
|
|
299
307
|
# ARM (32-bit) is 32-bit.
|
|
308
|
+
# @return [Integer]
|
|
300
309
|
def bits
|
|
301
310
|
32
|
|
302
311
|
end
|
|
@@ -13,10 +13,13 @@ module OneGadget
|
|
|
13
13
|
# individual classes.
|
|
14
14
|
class ArmFamily < Processor
|
|
15
15
|
# ARM condition-code suffix (the +<cc>+ in +b<cc>+ / +b.<cc>+) mapped to a
|
|
16
|
-
# shared {Conditional::RELATION} predicate.
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
16
|
+
# shared {Conditional::RELATION} predicate. (x86 uses {X86::JCC}.)
|
|
17
|
+
# @example Aliases decode alike, the sign bit reads as a signed comparison,
|
|
18
|
+
# and overflow has no constraint form -- a branch on it aborts the path.
|
|
19
|
+
# COND['hs'] #=> :uge
|
|
20
|
+
# COND['cs'] #=> :uge
|
|
21
|
+
# COND['mi'] #=> :slt
|
|
22
|
+
# COND['vs'] #=> nil
|
|
20
23
|
COND = {
|
|
21
24
|
'eq' => :eq, 'ne' => :ne,
|
|
22
25
|
'hs' => :uge, 'cs' => :uge, 'lo' => :ult, 'cc' => :ult,
|
|
@@ -69,6 +72,23 @@ module OneGadget
|
|
|
69
72
|
OneGadget::Helper.hex(m[1] == 'lsl' ? value << Integer(m[2]) : value >> Integer(m[2]))
|
|
70
73
|
end
|
|
71
74
|
|
|
75
|
+
# +op2+ with the modifier this family may spell on it folded in. Yields for a
|
|
76
|
+
# modifier it does not model, so an unmodelled one aborts the candidate
|
|
77
|
+
# instead of being silently dropped.
|
|
78
|
+
# @param [String] op2 The operand the modifier applies to.
|
|
79
|
+
# @param [String, nil] mode The modifier, as written -- +nil+ for none.
|
|
80
|
+
# @return [String] The operand {Processor#arith} then reads.
|
|
81
|
+
# @example (aarch64, +x2+ holding +0x1+) A constant shift folds in, a
|
|
82
|
+
# sign-extension is taken whole, and a rotate is not modelled.
|
|
83
|
+
# modified_operand('x2', 'lsl 3') #=> '0x8'
|
|
84
|
+
# modified_operand('x2', 'sxtw') #=> 'x2'
|
|
85
|
+
# modified_operand('x2', 'ror 3') { :abort } #=> :abort
|
|
86
|
+
def modified_operand(op2, mode)
|
|
87
|
+
return op2 if mode.nil? || mode == 'sxtw'
|
|
88
|
+
|
|
89
|
+
shifted_operand(value_str(value_of(op2)), mode) || yield
|
|
90
|
+
end
|
|
91
|
+
|
|
72
92
|
# +add+/+sub+, shared by both families. Each allows the 2-operand shorthand,
|
|
73
93
|
# and each may carry a modifier on +op2+ -- a shift, or (aarch64) a
|
|
74
94
|
# sign-extension of its low half.
|
|
@@ -77,94 +97,22 @@ module OneGadget
|
|
|
77
97
|
# @param [String, nil] op2 The value to add, or nil in the 2-operand form.
|
|
78
98
|
# @param [String, nil] mode A modifier applied to +op2+ (see {#modified_operand}).
|
|
79
99
|
# @return [void]
|
|
80
|
-
def inst_add(dst, src, op2 = nil, mode = nil)
|
|
81
|
-
|
|
82
|
-
# +sub dst, src, op2+. See {#inst_add} for the operands.
|
|
83
|
-
# @return [void]
|
|
84
|
-
def inst_sub(dst, src, op2 = nil, mode = nil) = arith(:-, dst, src, op2, mode)
|
|
85
|
-
|
|
86
|
-
# Add or subtract, and store the result.
|
|
87
|
-
#
|
|
88
|
-
# A sum of two values neither of which is known folds into no base+offset,
|
|
89
|
-
# so it is named as the operation it is -- a candidate deriving a pointer
|
|
90
|
-
# that way still says what the caller has to arrange. That is the only
|
|
91
|
-
# fallback: an offset from a known base stays a base+offset, which the rest
|
|
92
|
-
# of the emulator can resolve against tracked memory.
|
|
93
|
-
# @param [Symbol] op +:++ or +:-+.
|
|
94
|
-
# @return [void]
|
|
95
|
-
# @raise [OneGadget::Error::UnsupportedInstructionArgumentError]
|
|
96
|
-
# When the modifier, or the result, is not one this emulator can name.
|
|
97
|
-
def arith(op, dst, src, op2, mode)
|
|
98
|
-
check_register!(dst)
|
|
99
|
-
src, op2 = shorthand(dst, src, op2)
|
|
100
|
-
lhs = value_of(src)
|
|
101
|
-
rhs = modified_operand(value_of(op2), mode) { raise_unsupported(op, dst, src, op2, mode) }
|
|
102
|
-
|
|
103
|
-
result = offset_result(op, lhs, rhs)
|
|
104
|
-
# The stack pointer has to stay an offset from itself: every tracked
|
|
105
|
-
# stack slot is keyed on it, and a candidate that reads one back after
|
|
106
|
-
# allocating a variable-size frame would be answered from the wrong
|
|
107
|
-
# place. Such a frame also puts the array a gadget builds at an address
|
|
108
|
-
# only a register the caller supplies decides, which no constraint this
|
|
109
|
-
# emulator emits states.
|
|
110
|
-
result ||= operation_result(op, lhs, rhs) unless dst == sp
|
|
111
|
-
raise_unsupported(op, dst, src, op2) if result.nil?
|
|
112
|
-
|
|
113
|
-
registers[dst] = result
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# +lhs op rhs+ when the result is an offset from +lhs+'s base, which
|
|
117
|
-
# {Lambda} expresses directly. +nil+ when it is not, leaving the caller to
|
|
118
|
-
# name the operation instead.
|
|
119
|
-
# @return [Lambda, Integer, nil]
|
|
120
|
-
def offset_result(op, lhs, rhs)
|
|
121
|
-
return lhs.send(op, rhs) if rhs.is_a?(Integer)
|
|
122
|
-
# Adding a known offset to an unknown value is the same value shifted;
|
|
123
|
-
# subtracting from one is not, so only addition commutes here.
|
|
124
|
-
return rhs + lhs if op == :+ && lhs.is_a?(Integer)
|
|
125
|
-
|
|
126
|
-
nil
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
# +value+ with an operand modifier applied. +nil+ +mode+ is the bare
|
|
130
|
-
# operand. Yields, rather than returning, for a modifier this emulator does
|
|
131
|
-
# not model, so an unmodelled one aborts instead of being silently dropped.
|
|
132
|
-
# @example (aarch64) +add x0, x1, w2, sxtw+ -- +mode+ is +"sxtw"+
|
|
133
|
-
# @example (arm) +add r0, r1, r2, lsl 3+ -- +mode+ is +"lsl 3"+
|
|
134
|
-
# @return [Lambda, Integer]
|
|
135
|
-
def modified_operand(value, mode)
|
|
136
|
-
return value if mode.nil? || mode == 'sxtw' # a sign-extension we take whole
|
|
137
|
-
|
|
138
|
-
shifted = shifted_operand(value_str(value), mode)
|
|
139
|
-
return yield if shifted.nil?
|
|
140
|
-
|
|
141
|
-
Integer(shifted)
|
|
100
|
+
def inst_add(dst, src, op2 = nil, mode = nil)
|
|
101
|
+
arith(:+, dst, src, modified_operand(op2, mode) { raise_unsupported(:+, dst, src, op2, mode) })
|
|
142
102
|
end
|
|
143
103
|
|
|
144
|
-
#
|
|
145
|
-
# allow the 2-operand shorthand, so +src+ may be the only operand given.
|
|
146
|
-
# @param [String] name The mnemonic, and the {DATA_OPS} key naming its operator.
|
|
147
|
-
# @param [String] dst The destination register.
|
|
148
|
-
# @param [String] src The left operand, or the only one given (see {#shorthand}).
|
|
149
|
-
# @param [String, nil] op2 The right operand, or nil in the 2-operand form.
|
|
104
|
+
# +sub dst, src, op2+. See {#inst_add} for the operands.
|
|
150
105
|
# @return [void]
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def data_op(name, dst, src, op2)
|
|
154
|
-
check_register!(dst)
|
|
155
|
-
src, op2 = shorthand(dst, src, op2)
|
|
156
|
-
result = operation_result(DATA_OPS.fetch(name), value_of(src), value_of(op2))
|
|
157
|
-
raise_unsupported(name, dst, src, op2) if result.nil?
|
|
158
|
-
|
|
159
|
-
# A shift can push bits past the register width, which the arbitrary-
|
|
160
|
-
# precision fold above would otherwise keep.
|
|
161
|
-
registers[dst] = result.is_a?(Integer) ? result & width_mask : result
|
|
106
|
+
def inst_sub(dst, src, op2 = nil, mode = nil)
|
|
107
|
+
arith(:-, dst, src, modified_operand(op2, mode) { raise_unsupported(:-, dst, src, op2, mode) })
|
|
162
108
|
end
|
|
163
109
|
|
|
164
110
|
# Each {DATA_OPS} mnemonic handled the one way, since they differ only in the
|
|
165
|
-
# operator applied (see {#data_op} for the operands).
|
|
111
|
+
# operator applied (see {Processor#data_op} for the operands).
|
|
166
112
|
%w[and orr eor lsl lsr].each do |name|
|
|
167
|
-
define_method(:"inst_#{name}")
|
|
113
|
+
define_method(:"inst_#{name}") do |dst, src, op2 = nil|
|
|
114
|
+
data_op(DATA_OPS.fetch(name), dst, src, op2, name:)
|
|
115
|
+
end
|
|
168
116
|
end
|
|
169
117
|
|
|
170
118
|
# +bic dst, src, op2+ clears the bits +op2+ sets, which is +and+ against its
|
|
@@ -176,7 +124,8 @@ module OneGadget
|
|
|
176
124
|
# @return [void]
|
|
177
125
|
def inst_bic(dst, src, op2 = nil)
|
|
178
126
|
src, op2 = shorthand(dst, src, op2)
|
|
179
|
-
data_op(
|
|
127
|
+
data_op(DATA_OPS.fetch('and'), dst, src,
|
|
128
|
+
OneGadget::Helper.hex(complement('bic', op2, dst, src, op2)), name: 'and')
|
|
180
129
|
end
|
|
181
130
|
|
|
182
131
|
# +mvn dst, op2+ is that complement on its own.
|
|
@@ -189,51 +138,6 @@ module OneGadget
|
|
|
189
138
|
registers[dst] = complement('mvn', op2, dst, op2)
|
|
190
139
|
end
|
|
191
140
|
|
|
192
|
-
# +op2+ with every bit flipped. Only a concrete value has a complement this
|
|
193
|
-
# emulator can name; a symbolic one aborts rather than being recorded as a
|
|
194
|
-
# mask it isn't.
|
|
195
|
-
# @param [String] name The mnemonic to report an abort against.
|
|
196
|
-
# @param [String] op2 The operand to complement.
|
|
197
|
-
# @param [Array<String>] reported The operands to name in that abort.
|
|
198
|
-
# @return [Integer] +op2+ complemented, within the register width.
|
|
199
|
-
def complement(name, op2, *reported)
|
|
200
|
-
value = value_of(op2)
|
|
201
|
-
raise_unsupported(name, *reported) unless value.is_a?(Integer)
|
|
202
|
-
|
|
203
|
-
~value & width_mask
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# Every bit of a register, for masking a result back to its width.
|
|
207
|
-
# @return [Integer]
|
|
208
|
-
def width_mask = (1 << self.class.bits) - 1
|
|
209
|
-
|
|
210
|
-
# The value of an operand. {Arm} overrides it for +pc+, whose value depends
|
|
211
|
-
# on the address of the instruction reading it.
|
|
212
|
-
# @param [String] arg The operand, as written.
|
|
213
|
-
# @return [OneGadget::Emulators::Lambda, Integer] Its current value.
|
|
214
|
-
def value_of(arg) = arg_to_lambda(arg)
|
|
215
|
-
|
|
216
|
-
# Expand a 2-operand data-processing form into its (src, op2) operands:
|
|
217
|
-
# +add dst, op2+ is shorthand for +add dst, dst, op2+, while an explicit
|
|
218
|
-
# 3-operand form is passed through unchanged.
|
|
219
|
-
# @param [String] dst The destination register, which the 2-operand form
|
|
220
|
-
# also reads as its left operand.
|
|
221
|
-
# @param [String] src The left operand, or the right one in the 2-operand form.
|
|
222
|
-
# @param [String, nil] op2 The right operand, or nil in the 2-operand form.
|
|
223
|
-
# @return [(String, String)] The left and right operands.
|
|
224
|
-
# @example
|
|
225
|
-
# shorthand('r0', 'r4', nil) # 2-operand: add r0, r4
|
|
226
|
-
# #=> ['r0', 'r4']
|
|
227
|
-
# shorthand('r0', 'r4', '8') # 3-operand: add r0, r4, 8
|
|
228
|
-
# #=> ['r4', '8']
|
|
229
|
-
def shorthand(dst, src, op2)
|
|
230
|
-
op2.nil? ? [dst, src] : [src, op2]
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
# An instruction with no effect this emulator models anything of.
|
|
234
|
-
# @return [void]
|
|
235
|
-
def inst_nop(*); end
|
|
236
|
-
|
|
237
141
|
# A memory barrier orders accesses without changing any value, so a path
|
|
238
142
|
# crossing one carries on unchanged. The operand naming its scope (+dmb ish+)
|
|
239
143
|
# says which accesses, not what they hold.
|
|
@@ -249,21 +153,6 @@ module OneGadget
|
|
|
249
153
|
dispatch_safe_call(addr)
|
|
250
154
|
end
|
|
251
155
|
|
|
252
|
-
# Track a store: write +values+ (one per word from +dst_l+) into the stack
|
|
253
|
-
# {#resolve_address} resolves +dst_l+ to, and require +dst_l+
|
|
254
|
-
# writable -- unless it is a pure +sp+ store. +sp+ is invariantly the
|
|
255
|
-
# writable stack; the frame pointer only conventionally is, so a store
|
|
256
|
-
# through it stays a real precondition (like amd64's +writable: rbp+imm+).
|
|
257
|
-
# Shared by +str+ (one value) and +stp+ (two).
|
|
258
|
-
# @param [OneGadget::Emulators::Lambda] dst_l The destination, zero-deref.
|
|
259
|
-
# @param [Array<OneGadget::Emulators::Lambda, Integer>] values One per word.
|
|
260
|
-
# @return [void]
|
|
261
|
-
def track_write(dst_l, *values)
|
|
262
|
-
stack, offset = resolve_address(dst_l)
|
|
263
|
-
values.each_with_index { |v, i| stack[offset + size_t * i] = v } if stack
|
|
264
|
-
add_writable(dst_l) unless stack.equal?(sp_based_stack)
|
|
265
|
-
end
|
|
266
|
-
|
|
267
156
|
# A byte load. The address is read like any other, so what the caller has to
|
|
268
157
|
# arrange about it is recorded the same way -- but one byte of a word is not
|
|
269
158
|
# a value this emulator can name, so the register holds what a call would
|
|
@@ -281,20 +170,6 @@ module OneGadget
|
|
|
281
170
|
def handle_cbz(ops, negate:)
|
|
282
171
|
branch_on_zero(ops[1].to_i(16), ops[0], negate:)
|
|
283
172
|
end
|
|
284
|
-
|
|
285
|
-
# Replace register tokens that currently hold a concrete integer with that
|
|
286
|
-
# integer, so a register-indexed memory operand becomes an offset one the
|
|
287
|
-
# Lambda parser handles.
|
|
288
|
-
# @example
|
|
289
|
-
# # with the index register currently holding 0xd8
|
|
290
|
-
# resolve_int_regs('[r8, r2]') #=> '[r8, 0xd8]'
|
|
291
|
-
# resolve_int_regs('[x8, x2]') #=> '[x8, 0xd8]'
|
|
292
|
-
def resolve_int_regs(str)
|
|
293
|
-
str.gsub(/[a-z]+\d*/) do |tok|
|
|
294
|
-
v = registers[tok] if register?(tok)
|
|
295
|
-
v.is_a?(Integer) ? OneGadget::Helper.hex(v) : tok
|
|
296
|
-
end
|
|
297
|
-
end
|
|
298
173
|
end
|
|
299
174
|
end
|
|
300
175
|
end
|
|
@@ -11,10 +11,6 @@ module OneGadget
|
|
|
11
11
|
# actual taken/not-taken path (see {OneGadget::Fetchers::Base#candidates}), and
|
|
12
12
|
# the emulator turns the branch decision into a gadget constraint.
|
|
13
13
|
#
|
|
14
|
-
# Branches are resolved with one line of look-ahead: at the branch we record a
|
|
15
|
-
# pending decision, and on the next line we compare that line's address to the
|
|
16
|
-
# branch target to learn whether the stitched path took the branch.
|
|
17
|
-
#
|
|
18
14
|
# The including class (an {OneGadget::Emulators::Processor} subclass) must
|
|
19
15
|
# provide +registers+ and +register?+ (operand lookup), +operands(cmd)+ (the
|
|
20
16
|
# arch's operand splitter), +self.class.bits+ (32/64, for the signedness cast),
|
|
@@ -27,8 +23,11 @@ module OneGadget
|
|
|
27
23
|
module Conditional
|
|
28
24
|
# Taken-semantics of each supported branch condition, keyed by a predicate
|
|
29
25
|
# named after the comparison it encodes (the LLVM +icmp+ names): a leading
|
|
30
|
-
# +u+ = unsigned, +s+ = signed.
|
|
31
|
-
#
|
|
26
|
+
# +u+ = unsigned, +s+ = signed.
|
|
27
|
+
# @example The value is the relation, and the cast its operands take.
|
|
28
|
+
# RELATION[:eq] #=> ['==', nil]
|
|
29
|
+
# RELATION[:ult] #=> ['<', :u]
|
|
30
|
+
# RELATION[:sge] #=> ['>=', :s]
|
|
32
31
|
# :eq :ne equality
|
|
33
32
|
# :ult :ule :ugt :uge unsigned < <= > >=
|
|
34
33
|
# :slt :sle :sgt :sge signed < <= > >=
|
|
@@ -42,11 +41,12 @@ module OneGadget
|
|
|
42
41
|
NEGATE = { '==' => '!=', '!=' => '==', '>=' => '<', '<' => '>=', '>' => '<=', '<=' => '>' }.freeze
|
|
43
42
|
|
|
44
43
|
# The flag-setting compares we model, keyed by the ALU operation the compare
|
|
45
|
-
# performs
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
44
|
+
# performs. An arch maps its own mnemonics onto these (its +COMPARES+), so
|
|
45
|
+
# adding one needs no change here.
|
|
46
|
+
# @example Whether magnitude conditions are sound after it, and what renders
|
|
47
|
+
# its constraint text.
|
|
48
|
+
# COMPARE_OPS[:sub] #=> { ordered: true, render: :render_sub }
|
|
49
|
+
# COMPARE_OPS.keys #=> [:sub, :add, :and]
|
|
50
50
|
COMPARE_OPS = {
|
|
51
51
|
sub: { ordered: true, render: :render_sub }, # subtraction: flags from lhs - rhs
|
|
52
52
|
add: { ordered: true, render: :render_add }, # addition: flags from lhs + rhs
|
|
@@ -56,9 +56,9 @@ module OneGadget
|
|
|
56
56
|
# A value comparison's zero literal, always hex. Pointer operands render
|
|
57
57
|
# +NULL+ (chosen by the arg resolvers); every value-context zero here is
|
|
58
58
|
# +0x0+, so a bare +0+ (and objdump's arch-specific +#0+ vs +0x0+) never leaks
|
|
59
|
-
# into a constraint.
|
|
59
|
+
# into a constraint. Recorded here and read back by {Constraints}, whose drop
|
|
60
|
+
# rules recognise a compare against zero by it.
|
|
60
61
|
ZERO = '0x0'
|
|
61
|
-
private_constant :ZERO
|
|
62
62
|
|
|
63
63
|
# Record a compare so a following conditional branch can be rendered.
|
|
64
64
|
# Normally reached through {#handle_compare}; call it directly only when an
|
|
@@ -188,6 +188,7 @@ module OneGadget
|
|
|
188
188
|
# # fall-through path emits x0 != 0 ; taken path emits x0 == 0
|
|
189
189
|
# @example x86 reuses it for +jrcxz+/+jecxz+/+jcxz+ (always branch-if-zero)
|
|
190
190
|
# branch_on_zero(0x4a200, 'rcx', negate: false)
|
|
191
|
+
# @return [true] Registering the branch is how the line is handled.
|
|
191
192
|
def branch_on_zero(target, operand, negate:)
|
|
192
193
|
reg = operand_str(operand)
|
|
193
194
|
hit = negate ? '!=' : '==' # taken (not negated) => reg == 0
|
|
@@ -205,6 +206,7 @@ module OneGadget
|
|
|
205
206
|
# @example aarch64 +tbz w0, #4, 4a200+ - branch taken when bit 4 of +w0+ is 0
|
|
206
207
|
# branch_on_bit(0x4a200, 'w0', 4, negate: false) #=> true
|
|
207
208
|
# # taken path emits (w0 & 0x10) == 0 ; fall-through emits (w0 & 0x10) != 0
|
|
209
|
+
# @return [true] Registering the branch is how the line is handled.
|
|
208
210
|
def branch_on_bit(target, operand, bit, negate:)
|
|
209
211
|
reg = operand_str(operand)
|
|
210
212
|
mask = OneGadget::Helper.hex(1 << bit)
|
|
@@ -227,6 +229,9 @@ module OneGadget
|
|
|
227
229
|
# end
|
|
228
230
|
# # if the previous line did +branch_on_compare(:ne, 0x4a200)+ and this
|
|
229
231
|
# # +cmd+ sits at 0x4a200, the branch was taken; otherwise it fell through
|
|
232
|
+
# @return [void]
|
|
233
|
+
# @raise [OneGadget::Error::InfeasiblePathError]
|
|
234
|
+
# When the resolved relation cannot hold with the ones already recorded.
|
|
230
235
|
def resolve_pending_branch(cmd)
|
|
231
236
|
return if @pending.nil?
|
|
232
237
|
|
|
@@ -244,6 +249,8 @@ module OneGadget
|
|
|
244
249
|
# each register's current value, so two constraints printing the same left
|
|
245
250
|
# side really are about the same tracked value (and a differing signedness
|
|
246
251
|
# cast is part of that text, keeping incomparable ones apart).
|
|
252
|
+
# @param [String] expr The left side, as rendered.
|
|
253
|
+
# @return [Array<(String, String, String)>] The comparisons recorded on it.
|
|
247
254
|
def comparisons_on(expr)
|
|
248
255
|
@constraints.filter_map { |type, obj| obj if type == :cmp && obj.first == expr }
|
|
249
256
|
end
|
|
@@ -304,15 +311,14 @@ module OneGadget
|
|
|
304
311
|
["#{cast}#{lhs}", op, rhs]
|
|
305
312
|
end
|
|
306
313
|
|
|
307
|
-
# Addition (+:add+): the flags reflect +lhs + rhs+
|
|
308
|
-
#
|
|
309
|
-
#
|
|
310
|
-
#
|
|
311
|
-
#
|
|
312
|
-
#
|
|
313
|
-
#
|
|
314
|
-
#
|
|
315
|
-
# satisfied by x0 == 0, which "(x0 + 0x1000) <= 0" would wrongly exclude.
|
|
314
|
+
# Addition (+:add+): the flags reflect +lhs + rhs+ against 0, except under an
|
|
315
|
+
# unsigned condition, which reads the carry instead -- set exactly when the
|
|
316
|
+
# sum wraps, that is when +lhs >= -rhs+.
|
|
317
|
+
# @example Read as an equality, then unsigned -- the second is glibc's "the
|
|
318
|
+
# syscall didn't return -errno" check, which +x0 == 0+ satisfies and
|
|
319
|
+
# +(x0 + 0x1000) <= 0+ would wrongly exclude.
|
|
320
|
+
# render_add(nil, 'x0', '0x1000', :ne, nil) #=> ['(x0 + 0x1000)', :ne, '0x0']
|
|
321
|
+
# render_add(nil, 'x0', '0x1000', :le, :u) #=> ['x0', :le, '0xfffffffffffff000']
|
|
316
322
|
def render_add(cast, lhs, rhs, op, sign)
|
|
317
323
|
return ["#{cast}(#{lhs} + #{rhs})", op, ZERO] unless sign == :u && negatable?(rhs)
|
|
318
324
|
|