Skip to content

Commit

Permalink
Added PowerPC switch detection pass
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandro Sanchez Bach committed Jun 14, 2017
1 parent 07572ce commit 4a0fd49
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ published at EuroS&P 2017.

Requirements:
- libcapstone (tested with 3.0)
- libbfd (multiarch)
- libbfd-multiarch

Platform:
- Tested on Ubuntu 15.10 and 16.04
Expand Down
129 changes: 129 additions & 0 deletions cfg.cc
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,132 @@ CFG::mark_jmptab_as_data(uint64_t start, uint64_t end)
}


void
CFG::find_switches_ppc()
{
BB *bb, *cc;
Edge *conflict_edge;
Section *target_sec;
int scale;
unsigned offset;
uint64_t jmptab_addr, jmptab_idx, jmptab_end, case_addr;
uint32_t *jmptab32;
uint64_t *jmptab64;

/* Instructions can get reordered, so we emulate the ISA subset relevant for the patterns below,
* clearing the intermediate register values with with -1 if the result is irrelevant or undefined. */
int64_t registers[32];

/* Assume the jump-table entries are the same width as the GPRs */
scale = this->binary->bits / 8;

for(auto &kv: this->start2bb) {
bb = kv.second;
jmptab_addr = 0;
target_sec = NULL;
/* If this BB ends in an indirect jmp, scan the BB for what looks like
* instructions loading a target from a jump table */
if(bb->insns.back().edge_type() == Edge::EDGE_TYPE_JMP_INDIRECT) {
target_sec = bb->section;
for(auto &ins: bb->insns) {
if(ins.operands.size() < 2) {
continue;
}
/* Pattern #1 (32-bit)
* Load the address from its word halves. Following variants are supported:
* - Using addis/addi (gcc):
* lis rN, .L@ha
* addi rN, rN, L@l
* - Using addis/ori:
* lis rN, .L@ha
* ori rN, rN, .L@l */
if(ins.id == PPC_INS_LIS) {
int64_t dst = ins.operands[0].ppc_value.reg - PPC_REG_R0;
int64_t imm = ins.operands[1].ppc_value.imm;
assert(dst < 32);
registers[dst] = imm << 16;
}
else if(ins.id == PPC_INS_ADDI || ins.id == PPC_INS_ORI) {
int64_t lhs = ins.operands[1].ppc_value.reg - PPC_REG_R0;
int64_t rhs = ins.operands[2].ppc_value.imm;
assert(lhs < 32);
if (registers[lhs] != -1) {
jmptab_addr = (uint64_t)(registers[lhs] | rhs);
break;
}
}
else if(ins.operands[0].type == Operand::OP_TYPE_REG
&& ins.operands[0].ppc_value.reg >= PPC_REG_R0
&& ins.operands[0].ppc_value.reg <= PPC_REG_R31) {
int64_t dst = ins.operands[0].ppc_value.reg - PPC_REG_R0;
registers[dst] = -1;
}
}
}

if(jmptab_addr) {
jmptab_end = 0;
for(auto &sec: this->binary->sections) {
if(sec.contains(jmptab_addr)) {
verbose(4, "parsing jump table at 0x%016jx (jump at 0x%016jx)",
jmptab_addr, bb->insns.back().start);
jmptab_idx = jmptab_addr-sec.vma;
jmptab_end = jmptab_addr;
jmptab32 = (uint32_t*)&sec.bytes[jmptab_idx];
jmptab64 = (uint64_t*)&sec.bytes[jmptab_idx];
while(1) {
if((jmptab_idx+scale) >= sec.size) break;
jmptab_end += scale;
jmptab_idx += scale;
switch(scale) {
case 4:
case_addr = (*jmptab32++);
break;
case 8:
case_addr = (*jmptab64++);
break;
default:
print_warn("Unexpected scale factor in memory operand: %d", scale);
case_addr = 0;
break;
}
if(!case_addr) break;
if(!target_sec->contains(case_addr)) {
break;
} else {
cc = this->get_bb(case_addr, &offset);
if(!cc) break;
conflict_edge = NULL;
for(auto &e: cc->ancestors) {
if(e.is_switch) {
conflict_edge = &e;
break;
}
}
if(conflict_edge && (conflict_edge->jmptab <= jmptab_addr)) {
verbose(3, "removing switch edge 0x%016jx -> 0x%016jx (detected overlapping jump table or case)",
conflict_edge->src->insns.back().start, case_addr);
unlink_edge(conflict_edge->src, cc);
conflict_edge = NULL;
}
if(!conflict_edge) {
verbose(3, "adding switch edge 0x%016jx -> 0x%016jx", bb->insns.back().start, case_addr);
link_bbs(Edge::EDGE_TYPE_JMP_INDIRECT, bb, case_addr, jmptab_addr);
}
}
}
break;
}
}

if(jmptab_addr && jmptab_end) {
mark_jmptab_as_data(jmptab_addr, jmptab_end);
}
}
}
}


void
CFG::find_switches_x86()
{
Expand Down Expand Up @@ -302,6 +428,9 @@ CFG::find_switches()
verbose(1, "starting switch analysis");

switch(this->binary->arch) {
case Binary::ARCH_PPC:
find_switches_ppc();
break;
case Binary::ARCH_X86:
find_switches_x86();
break;
Expand Down
1 change: 1 addition & 0 deletions cfg.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class CFG {

/* pass: switch detection */
void mark_jmptab_as_data (uint64_t start, uint64_t end);
void find_switches_ppc ();
void find_switches_x86 ();
void find_switches ();

Expand Down
24 changes: 20 additions & 4 deletions disasm-ppc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ is_cs_unconditional_jmp_ins(cs_insn *ins)
switch(ins->id) {
case PPC_INS_B:
case PPC_INS_BA:
case PPC_INS_BCTR:
return 1;
case PPC_INS_BCCTR:
assert(ins->detail->ppc.op_count >= 2);
Expand All @@ -119,7 +120,7 @@ is_cs_unconditional_jmp_ins(cs_insn *ins)
if (bo == 20 && bi == 0) {
return 1;
}
return 1;
return 0;
default:
return 0;
}
Expand Down Expand Up @@ -174,6 +175,21 @@ is_cs_privileged_ins(cs_insn *ins)
}


static int
is_cs_indirect_ins(cs_insn *ins)
{
switch(ins->id) {
case PPC_INS_BCTR:
case PPC_INS_BCTRL:
case PPC_INS_BCCTR:
case PPC_INS_BCCTRL:
return 1;
default:
return 0;
}
}


static uint8_t
cs_to_nucleus_op_type(ppc_op_type op)
{
Expand All @@ -195,7 +211,7 @@ cs_to_nucleus_op_type(ppc_op_type op)
int
nucleus_disasm_bb_ppc(Binary *bin, DisasmSection *dis, BB *bb)
{
int init, ret, jmp, cflow, cond, call, nop, only_nop, priv, trap, ndisassembled;
int init, ret, jmp, cflow, indir, cond, call, nop, only_nop, priv, trap, ndisassembled;
csh cs_dis;
cs_mode cs_mode_flags;
cs_insn *cs_ins;
Expand Down Expand Up @@ -265,6 +281,7 @@ nucleus_disasm_bb_ppc(Binary *bin, DisasmSection *dis, BB *bb)
cflow = is_cs_cflow_ins(cs_ins);
call = is_cs_call_ins(cs_ins);
priv = is_cs_privileged_ins(cs_ins);
indir = is_cs_indirect_ins(cs_ins);

if(!ndisassembled && nop) only_nop = 1; /* group nop instructions together */
if(!only_nop && nop) break;
Expand Down Expand Up @@ -298,6 +315,7 @@ nucleus_disasm_bb_ppc(Binary *bin, DisasmSection *dis, BB *bb)
if(cond) ins->flags |= Instruction::INS_FLAG_COND;
if(cflow) ins->flags |= Instruction::INS_FLAG_CFLOW;
if(call) ins->flags |= Instruction::INS_FLAG_CALL;
if(indir) ins->flags |= Instruction::INS_FLAG_INDIRECT;

for(i = 0; i < cs_ins->detail->ppc.op_count; i++) {
cs_op = &cs_ins->detail->ppc.operands[i];
Expand All @@ -308,11 +326,9 @@ nucleus_disasm_bb_ppc(Binary *bin, DisasmSection *dis, BB *bb)
op->ppc_value.imm = cs_op->imm;
} else if(op->type == Operand::OP_TYPE_REG) {
op->ppc_value.reg = (ppc_reg)cs_op->reg;
if(cflow) ins->flags |= Instruction::INS_FLAG_INDIRECT;
} else if(op->type == Operand::OP_TYPE_MEM) {
op->ppc_value.mem.base = cs_op->mem.base;
op->ppc_value.mem.disp = cs_op->mem.disp;
if(cflow) ins->flags |= Instruction::INS_FLAG_INDIRECT;
}
}

Expand Down

0 comments on commit 4a0fd49

Please sign in to comment.