1// copyright notice, this list of conditions and the following disclaimer
2// in the documentation and/or other materials provided with the
3// distribution.
4// * Neither the name of Google Inc. nor the names of its
5// contributors may be used to endorse or promote products derived from
6// this software without specific prior written permission.
7//
8// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
9// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
10// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
11// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
12// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
13// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
14// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
15// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
16// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
17// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
18// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19
20// disassembler_x86.cc: simple x86 disassembler.
21//
22// Provides single step disassembly of x86 bytecode and flags instructions
23// that utilize known bad register values.
24//
25// Author: Cris Neckar
26
27#include "processor/disassembler_x86.h"
28
29#include <string.h>
30
31namespace google_breakpad {
32
33DisassemblerX86::DisassemblerX86(const uint8_t* bytecode,
34 uint32_t size,
35 uint32_t virtual_address) :
36 bytecode_(bytecode),
37 size_(size),
38 virtual_address_(virtual_address),
39 current_byte_offset_(0),
40 current_inst_offset_(0),
41 instr_valid_(false),
42 register_valid_(false),
43 pushed_bad_value_(false),
44 end_of_block_(false),
45 flags_(0) {
46 libdis::x86_init(libdis::opt_none, NULL, NULL);
47}
48
49DisassemblerX86::~DisassemblerX86() {
50 if (instr_valid_)
51 libdis::x86_oplist_free(&current_instr_);
52
53 libdis::x86_cleanup();
54}
55
56uint32_t DisassemblerX86::NextInstruction() {
57 if (instr_valid_)
58 libdis::x86_oplist_free(&current_instr_);
59
60 if (current_byte_offset_ >= size_) {
61 instr_valid_ = false;
62 return 0;
63 }
64 uint32_t instr_size = 0;
65 instr_size = libdis::x86_disasm((unsigned char*)bytecode_, size_,
66 virtual_address_, current_byte_offset_,
67 &current_instr_);
68 if (instr_size == 0) {
69 instr_valid_ = false;
70 return 0;
71 }
72
73 current_byte_offset_ += instr_size;
74 current_inst_offset_++;
75 instr_valid_ = libdis::x86_insn_is_valid(&current_instr_);
76 if (!instr_valid_)
77 return 0;
78
79 if (current_instr_.type == libdis::insn_return)
80 end_of_block_ = true;
81 libdis::x86_op_t* src = libdis::x86_get_src_operand(&current_instr_);
82 libdis::x86_op_t* dest = libdis::x86_get_dest_operand(&current_instr_);
83
84 if (register_valid_) {
85 switch (current_instr_.group) {
86 // Flag branches based off of bad registers and calls that occur
87 // after pushing bad values.
88 case libdis::insn_controlflow:
89 switch (current_instr_.type) {
90 case libdis::insn_jmp:
91 case libdis::insn_jcc:
92 case libdis::insn_call:
93 case libdis::insn_callcc:
94 if (dest) {
95 switch (dest->type) {
96 case libdis::op_expression:
97 if (dest->data.expression.base.id == bad_register_.id)
98 flags_ |= DISX86_BAD_BRANCH_TARGET;
99 break;
100 case libdis::op_register:
101 if (dest->data.reg.id == bad_register_.id)
102 flags_ |= DISX86_BAD_BRANCH_TARGET;
103 break;
104 default:
105 if (pushed_bad_value_ &&
106 (current_instr_.type == libdis::insn_call ||
107 current_instr_.type == libdis::insn_callcc))
108 flags_ |= DISX86_BAD_ARGUMENT_PASSED;
109 break;
110 }
111 }
112 break;
113 default:
114 break;
115 }
116 break;
117
118 // Flag block data operations that use bad registers for src or dest.
119 case libdis::insn_string:
120 if (dest && dest->type == libdis::op_expression &&
121 dest->data.expression.base.id == bad_register_.id)
122 flags_ |= DISX86_BAD_BLOCK_WRITE;
123 if (src && src->type == libdis::op_expression &&
124 src->data.expression.base.id == bad_register_.id)
125 flags_ |= DISX86_BAD_BLOCK_READ;
126 break;
127
128 // Flag comparisons based on bad data.
129 case libdis::insn_comparison:
130 if ((dest && dest->type == libdis::op_expression &&
131 dest->data.expression.base.id == bad_register_.id) ||
132 (src && src->type == libdis::op_expression &&
133 src->data.expression.base.id == bad_register_.id) ||
134 (dest && dest->type == libdis::op_register &&
135 dest->data.reg.id == bad_register_.id) ||
136 (src && src->type == libdis::op_register &&
137 src->data.reg.id == bad_register_.id))
138 flags_ |= DISX86_BAD_COMPARISON;
139 break;
140
141 // Flag any other instruction which derefs a bad register for
142 // src or dest.
143 default:
144 if (dest && dest->type == libdis::op_expression &&
145 dest->data.expression.base.id == bad_register_.id)
146 flags_ |= DISX86_BAD_WRITE;
147 if (src && src->type == libdis::op_expression &&
148 src->data.expression.base.id == bad_register_.id)
149 flags_ |= DISX86_BAD_READ;
150 break;
151 }
152 }
153
154 // When a register is marked as tainted check if it is pushed.
155 // TODO(cdn): may also want to check for MOVs into EBP offsets.
156 if (register_valid_ && dest && current_instr_.type == libdis::insn_push) {
157 switch (dest->type) {
158 case libdis::op_expression:
159 if (dest->data.expression.base.id == bad_register_.id ||
160 dest->data.expression.index.id == bad_register_.id)
161 pushed_bad_value_ = true;
162 break;
163 case libdis::op_register:
164 if (dest->data.reg.id == bad_register_.id)
165 pushed_bad_value_ = true;
166 break;
167 default:
168 break;
169 }
170 }
171
172 // Check if a tainted register value is clobbered.
173 // For conditional MOVs and XCHGs assume that
174 // there is a hit.
175 if (register_valid_) {
176 switch (current_instr_.type) {
177 case libdis::insn_xor:
178 if (src && src->type == libdis::op_register &&
179 dest && dest->type == libdis::op_register &&
180 src->data.reg.id == bad_register_.id &&
181 src->data.reg.id == dest->data.reg.id)
182 register_valid_ = false;
183 break;
184 case libdis::insn_pop:
185 case libdis::insn_mov:
186 case libdis::insn_movcc:
187 if (dest && dest->type == libdis::op_register &&
188 dest->data.reg.id == bad_register_.id)
189 register_valid_ = false;
190 break;
191 case libdis::insn_popregs:
192 register_valid_ = false;
193 break;
194 case libdis::insn_xchg:
195 case libdis::insn_xchgcc:
196 if (dest && dest->type == libdis::op_register &&
197 src && src->type == libdis::op_register) {
198 if (dest->data.reg.id == bad_register_.id)
199 memcpy(&bad_register_, &src->data.reg, sizeof(libdis::x86_reg_t));
200 else if (src->data.reg.id == bad_register_.id)
201 memcpy(&bad_register_, &dest->data.reg, sizeof(libdis::x86_reg_t));
202 }
203 break;
204 default:
205 break;
206 }
207 }
208
209 return instr_size;
210}
211
212bool DisassemblerX86::setBadRead() {
213 if (!instr_valid_)
214 return false;
215
216 libdis::x86_op_t* operand = libdis::x86_get_src_operand(&current_instr_);
217 if (!operand || operand->type != libdis::op_expression)
218 return false;
219
220 memcpy(&bad_register_, &operand->data.expression.base,
221 sizeof(libdis::x86_reg_t));
222 register_valid_ = true;
223 return true;
224}
225
226bool DisassemblerX86::setBadWrite() {
227 if (!instr_valid_)
228 return false;
229
230 libdis::x86_op_t* operand = libdis::x86_get_dest_operand(&current_instr_);
231 if (!operand || operand->type != libdis::op_expression)
232 return false;
233
234 memcpy(&bad_register_, &operand->data.expression.base,
235 sizeof(libdis::x86_reg_t));
236 register_valid_ = true;
237 return true;
238}
239
240} // namespace google_breakpad
241