// copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // disassembler_x86.cc: simple x86 disassembler. // // Provides single step disassembly of x86 bytecode and flags instructions // that utilize known bad register values. // // Author: Cris Neckar #include "processor/disassembler_x86.h" #include namespace google_breakpad { DisassemblerX86::DisassemblerX86(const uint8_t* bytecode, uint32_t size, uint32_t virtual_address) : bytecode_(bytecode), size_(size), virtual_address_(virtual_address), current_byte_offset_(0), current_inst_offset_(0), instr_valid_(false), register_valid_(false), pushed_bad_value_(false), end_of_block_(false), flags_(0) { libdis::x86_init(libdis::opt_none, NULL, NULL); } DisassemblerX86::~DisassemblerX86() { if (instr_valid_) libdis::x86_oplist_free(¤t_instr_); libdis::x86_cleanup(); } uint32_t DisassemblerX86::NextInstruction() { if (instr_valid_) libdis::x86_oplist_free(¤t_instr_); if (current_byte_offset_ >= size_) { instr_valid_ = false; return 0; } uint32_t instr_size = 0; instr_size = libdis::x86_disasm((unsigned char*)bytecode_, size_, virtual_address_, current_byte_offset_, ¤t_instr_); if (instr_size == 0) { instr_valid_ = false; return 0; } current_byte_offset_ += instr_size; current_inst_offset_++; instr_valid_ = libdis::x86_insn_is_valid(¤t_instr_); if (!instr_valid_) return 0; if (current_instr_.type == libdis::insn_return) end_of_block_ = true; libdis::x86_op_t* src = libdis::x86_get_src_operand(¤t_instr_); libdis::x86_op_t* dest = libdis::x86_get_dest_operand(¤t_instr_); if (register_valid_) { switch (current_instr_.group) { // Flag branches based off of bad registers and calls that occur // after pushing bad values. case libdis::insn_controlflow: switch (current_instr_.type) { case libdis::insn_jmp: case libdis::insn_jcc: case libdis::insn_call: case libdis::insn_callcc: if (dest) { switch (dest->type) { case libdis::op_expression: if (dest->data.expression.base.id == bad_register_.id) flags_ |= DISX86_BAD_BRANCH_TARGET; break; case libdis::op_register: if (dest->data.reg.id == bad_register_.id) flags_ |= DISX86_BAD_BRANCH_TARGET; break; default: if (pushed_bad_value_ && (current_instr_.type == libdis::insn_call || current_instr_.type == libdis::insn_callcc)) flags_ |= DISX86_BAD_ARGUMENT_PASSED; break; } } break; default: break; } break; // Flag block data operations that use bad registers for src or dest. case libdis::insn_string: if (dest && dest->type == libdis::op_expression && dest->data.expression.base.id == bad_register_.id) flags_ |= DISX86_BAD_BLOCK_WRITE; if (src && src->type == libdis::op_expression && src->data.expression.base.id == bad_register_.id) flags_ |= DISX86_BAD_BLOCK_READ; break; // Flag comparisons based on bad data. case libdis::insn_comparison: if ((dest && dest->type == libdis::op_expression && dest->data.expression.base.id == bad_register_.id) || (src && src->type == libdis::op_expression && src->data.expression.base.id == bad_register_.id) || (dest && dest->type == libdis::op_register && dest->data.reg.id == bad_register_.id) || (src && src->type == libdis::op_register && src->data.reg.id == bad_register_.id)) flags_ |= DISX86_BAD_COMPARISON; break; // Flag any other instruction which derefs a bad register for // src or dest. default: if (dest && dest->type == libdis::op_expression && dest->data.expression.base.id == bad_register_.id) flags_ |= DISX86_BAD_WRITE; if (src && src->type == libdis::op_expression && src->data.expression.base.id == bad_register_.id) flags_ |= DISX86_BAD_READ; break; } } // When a register is marked as tainted check if it is pushed. // TODO(cdn): may also want to check for MOVs into EBP offsets. if (register_valid_ && dest && current_instr_.type == libdis::insn_push) { switch (dest->type) { case libdis::op_expression: if (dest->data.expression.base.id == bad_register_.id || dest->data.expression.index.id == bad_register_.id) pushed_bad_value_ = true; break; case libdis::op_register: if (dest->data.reg.id == bad_register_.id) pushed_bad_value_ = true; break; default: break; } } // Check if a tainted register value is clobbered. // For conditional MOVs and XCHGs assume that // there is a hit. if (register_valid_) { switch (current_instr_.type) { case libdis::insn_xor: if (src && src->type == libdis::op_register && dest && dest->type == libdis::op_register && src->data.reg.id == bad_register_.id && src->data.reg.id == dest->data.reg.id) register_valid_ = false; break; case libdis::insn_pop: case libdis::insn_mov: case libdis::insn_movcc: if (dest && dest->type == libdis::op_register && dest->data.reg.id == bad_register_.id) register_valid_ = false; break; case libdis::insn_popregs: register_valid_ = false; break; case libdis::insn_xchg: case libdis::insn_xchgcc: if (dest && dest->type == libdis::op_register && src && src->type == libdis::op_register) { if (dest->data.reg.id == bad_register_.id) memcpy(&bad_register_, &src->data.reg, sizeof(libdis::x86_reg_t)); else if (src->data.reg.id == bad_register_.id) memcpy(&bad_register_, &dest->data.reg, sizeof(libdis::x86_reg_t)); } break; default: break; } } return instr_size; } bool DisassemblerX86::setBadRead() { if (!instr_valid_) return false; libdis::x86_op_t* operand = libdis::x86_get_src_operand(¤t_instr_); if (!operand || operand->type != libdis::op_expression) return false; memcpy(&bad_register_, &operand->data.expression.base, sizeof(libdis::x86_reg_t)); register_valid_ = true; return true; } bool DisassemblerX86::setBadWrite() { if (!instr_valid_) return false; libdis::x86_op_t* operand = libdis::x86_get_dest_operand(¤t_instr_); if (!operand || operand->type != libdis::op_expression) return false; memcpy(&bad_register_, &operand->data.expression.base, sizeof(libdis::x86_reg_t)); register_valid_ = true; return true; } } // namespace google_breakpad