Binary Ninja subleq plugin
from binaryninja import (Architecture, RegisterInfo, InstructionInfo, | |
InstructionTextToken, InstructionTextTokenType, InstructionTextTokenContext, | |
BranchType, | |
LowLevelILOperation, LLIL_TEMP, | |
LowLevelILLabel, | |
FlagRole, | |
LowLevelILFlagCondition, | |
log_error, | |
CallingConvention, | |
interaction, | |
PluginCommand, BackgroundTaskThread, | |
HighlightStandardColor | |
) | |
import struct | |
def hexr(a): | |
if a == 0x4: | |
return 'UserInput' | |
if a == 0x8: | |
return 'Output' | |
if a == 0xc: | |
return 'ReadBool' | |
if a == 0x10: | |
return 'WriteBool' | |
if a == 0x3d00: | |
return '*1' | |
return hex(a) | |
# Helper function to make tokens easier to make | |
def makeToken(tokenType, text, data=None): | |
tokenType = { | |
'i':InstructionTextTokenType.InstructionToken, | |
't':InstructionTextTokenType.TextToken, | |
'a':InstructionTextTokenType.PossibleAddressToken, | |
's':InstructionTextTokenType.OperandSeparatorToken | |
}[tokenType] | |
if data is None: | |
return InstructionTextToken(tokenType, text) | |
return InstructionTextToken(tokenType, text, data) | |
class Subleq(Architecture): | |
name = "subleq" | |
address_size = 4 | |
default_int_size = 4 | |
max_instr_length = 12 # Each instruction is 3 dwords | |
# SP register is required, even if we are not going to use it | |
regs = {'sp': RegisterInfo('sp', 2)} | |
stack_pointer = 'sp' | |
def perform_get_instruction_info(self,data,addr): | |
# If we can't decode an instruction return None | |
if len(data) < 12: | |
return None | |
# Unpack our operands from the data | |
a,b,c = struct.unpack('<3I',data[:12]) | |
# Create the InstructionInfo object for our instruction | |
res = InstructionInfo() | |
res.length = 12 | |
if c != 0: | |
if b == a: | |
# Unconditional branch jumps to integer index c | |
res.add_branch(BranchType.UnconditionalBranch, c*4) | |
else: | |
# True branch jumps to integer index c | |
res.add_branch(BranchType.TrueBranch, c*4) | |
# False branch continues to next instruction | |
res.add_branch(BranchType.FalseBranch, addr + 12) | |
return res | |
def perform_get_instruction_text(self, data, addr): | |
# If we can't decode an instruction return None | |
if len(data) < 12: | |
return None | |
# Unpack our operands from the data | |
a,b,c = struct.unpack('<3I',data[:4*3]) | |
tokens = [] | |
# Check for invalid instructions that would crash | |
if b*4 >= 0x4400 or a*4 >= 0x4400: | |
tokens = [] | |
tokens.append(makeToken('i', '{:7s}'.format('invalid'))) | |
return tokens, 4*3 | |
# Clear instruction to be less verbose | |
# clear [B] | |
elif a == b: | |
tokens = [] | |
tokens.append(makeToken('i', '{:7s}'.format('clear'))) | |
tokens.append(makeToken('t', '[')) | |
tokens.append(makeToken('a', hexr(b*4), b*4)) | |
tokens.append(makeToken('t', ']')) | |
# Normal sub instruction | |
# sub [B], [A] | |
else: | |
tokens.append(makeToken('i', '{:7s}'.format('sub'))) | |
tokens.append(makeToken('t', '[')) | |
tokens.append(makeToken('a', hexr(b*4), b*4)) | |
tokens.append(makeToken('t', ']')) | |
tokens.append(makeToken('s', ', ')) | |
tokens.append(makeToken('t', '[')) | |
tokens.append(makeToken('a', hexr(a*4), a*4)) | |
tokens.append(makeToken('t', ']')) | |
# Unconditional jump | |
# ; jmp C | |
if c != 0 and b == a: | |
tokens.append(makeToken('s', '; ')) | |
tokens.append(makeToken('i', '{:7s}'.format('jmp'))) | |
tokens.append(makeToken('a', hex(c*4), c*4)) | |
# Conditional jump | |
# ; jmp C if [B] <= 0 | |
elif c != 0: | |
tokens.append(makeToken('s', '; ')) | |
tokens.append(makeToken('i', '{:7s}'.format('jmp'))) | |
tokens.append(makeToken('a', hex(c*4), c*4)) | |
tokens.append(makeToken('s', ' if ')) | |
tokens.append(makeToken('t', '[')) | |
tokens.append(makeToken('a', hex(b*4), b*4)) | |
tokens.append(makeToken('t', ']')) | |
tokens.append(makeToken('t', ' <= 0')) | |
return tokens, 4*3 | |
# Full LLIL lifting for subleq | |
def perform_get_instruction_low_level_il(self, data, addr, il): | |
# If we can't decode an instruction return None | |
if len(data) < 12: | |
return None | |
# Unpack our operands from the data | |
a,b,c = struct.unpack('<3I',data[:4*3]) | |
# If this instruction would crash, ignore it | |
if b*4 >= 0x4400 or a*4 >= 0x4400: | |
il.append(il.nop()) | |
return 4*3 | |
# A, B, and C as pointers | |
addr_a = il.const_pointer(4, a*4) | |
addr_b = il.const_pointer(4, a*4) | |
addr_c = il.const_pointer(4, c*4) | |
# mem[A] and mem[B] pointers | |
mem_a = il.load(4, addr_a) | |
mem_b = il.load(4, addr_b) | |
# For a clear instruction just store 0 | |
if a == b: | |
# *B = 0 | |
store_b = il.store(4, addr_b, il.const(4,0)) | |
il.append(store_b) | |
# For normal operation, construct a subtraction | |
else: | |
# *B = *B - *A | |
sub_op = il.sub(4, mem_b, mem_a) | |
store_b = il.store(4, addr_b, sub_op) | |
il.append(store_b) | |
# Unconditional jump | |
if c != 0 and b == a: | |
# goto C | |
jmp = il.jump(addr_c) | |
il.append(jmp) | |
# Conditional jump | |
elif c != 0: | |
# See if we have marked the True jump target before | |
t_target = il.get_label_for_address(Architecture['subleq'], | |
il[il.const_pointer(4, c*4)].constant) | |
# Create the False jump label | |
f_target = LowLevelILLabel() | |
# If we have to create a jump IL for the True target | |
indirect = t_target is None | |
if indirect: | |
t_target = LowLevelILLabel() | |
less_op = il.compare_signed_less_equal(4, mem_b, il.const(4, 0)) | |
if_op = il.if_expr(less_op, t_target, f_target) | |
il.append(if_op) | |
# We need to create a jump to the true target if it doesn't exist | |
if indirect: | |
il.mark_label(t_target) | |
jmp = il.jump(addr_c) | |
il.append(jmp) | |
# Last is the fall though for the false target | |
il.mark_label(f_target) | |
return 12 | |
Subleq.register() | |
def markModifiableCode(bv, func): | |
# Loop over all instructions in the function | |
for t in (t for bb in func.basic_blocks for t in bb.disassembly_text): | |
# Find our sub tokens | |
if not t.tokens[0].text.startswith('sub '): | |
continue | |
addr = t.tokens[2].value | |
# Check if the address is in a basic block | |
bbs = bv.get_basic_blocks_at(addr) | |
if len(bbs) == 0: | |
continue | |
# Check that this address really is an instruction | |
for tt in bbs[0].disassembly_text: | |
if addr - tt.address >= 3 or addr - tt.address < 0: | |
continue | |
# Highlight it and add comments | |
bbs[0].function.set_user_instr_highlight(tt.address, | |
HighlightStandardColor.RedHighlightColor) | |
bbs[0].function.set_comment_at(tt.address, "Modified by 0x%x"%t.address) | |
func.set_comment_at(t.address, "Modifies code at 0x%x"%tt.address) | |
break | |
PluginCommand.register_for_function('Subleq check modifiable code', 'subleq', markModifiableCode) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment